This commit is contained in:
Wiesław Šoltés 2021-11-05 20:03:26 +01:00
Родитель 5cb3359b04
Коммит 8bb0c7ebbc
39 изменённых файлов: 423 добавлений и 250 удалений

Просмотреть файл

@ -54,10 +54,15 @@ ProjectSection(SolutionItems) = preProject
build\XUnit.props = build\XUnit.props
build\Newtonsoft.Json.props = build\Newtonsoft.Json.props
build\Avalonia.Controls.PanAndZoom.props = build\Avalonia.Controls.PanAndZoom.props
build\ReactiveUI.props = build\ReactiveUI.props
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "_build", "build\build\_build.csproj", "{0737F4A8-E674-4518-A362-0A78D03F8165}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NodeEditorAvalonia.ReactiveUI", "src\NodeEditorAvalonia.ReactiveUI\NodeEditorAvalonia.ReactiveUI.csproj", "{0866A0D7-055F-4FA0-8F9E-CED158F2DEA6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NodeEditorAvalonia.Model", "src\NodeEditorAvalonia.Model\NodeEditorAvalonia.Model.csproj", "{5DECE45D-598C-47F0-9776-2BE77A4572BC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -80,6 +85,14 @@ Global
{0737F4A8-E674-4518-A362-0A78D03F8165}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0737F4A8-E674-4518-A362-0A78D03F8165}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0737F4A8-E674-4518-A362-0A78D03F8165}.Release|Any CPU.Build.0 = Release|Any CPU
{0866A0D7-055F-4FA0-8F9E-CED158F2DEA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0866A0D7-055F-4FA0-8F9E-CED158F2DEA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0866A0D7-055F-4FA0-8F9E-CED158F2DEA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0866A0D7-055F-4FA0-8F9E-CED158F2DEA6}.Release|Any CPU.Build.0 = Release|Any CPU
{5DECE45D-598C-47F0-9776-2BE77A4572BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5DECE45D-598C-47F0-9776-2BE77A4572BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5DECE45D-598C-47F0-9776-2BE77A4572BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5DECE45D-598C-47F0-9776-2BE77A4572BC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{861D1DD2-FDE9-492E-AEF6-E00F92F61EBD} = {02D6D6DC-21FA-435F-9526-E6F635127483}
@ -89,5 +102,7 @@ Global
{51C5CC41-B8B0-4763-B12F-CA77914C75DB} = {17289397-391E-4F2E-A591-4AA3EEC80586}
{84BDAB45-41BD-4877-943C-81F9C9A6AF9A} = {17289397-391E-4F2E-A591-4AA3EEC80586}
{0737F4A8-E674-4518-A362-0A78D03F8165} = {17289397-391E-4F2E-A591-4AA3EEC80586}
{0866A0D7-055F-4FA0-8F9E-CED158F2DEA6} = {02D6D6DC-21FA-435F-9526-E6F635127483}
{5DECE45D-598C-47F0-9776-2BE77A4572BC} = {02D6D6DC-21FA-435F-9526-E6F635127483}
EndGlobalSection
EndGlobal

6
build/ReactiveUI.props Normal file
Просмотреть файл

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="reactiveui" Version="13.2.10" />
</ItemGroup>
</Project>

Просмотреть файл

@ -11,7 +11,7 @@
<FluentTheme Mode="Light" />
<StyleInclude Source="avares://NodeEditorAvalonia/Themes/Fluent.axaml" />
<StyleInclude Source="avares://NodeEditorAvalonia/Themes/NodeEditorFluentTheme.axaml" />
<Style Selector="Rectangle.node">
<Setter Property="Fill" Value="White" />

Просмотреть файл

@ -2,8 +2,8 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using NodeEditor.Model;
using NodeEditor.ViewModels;
using NodeEditorDemo.ViewModels;
namespace NodeEditorDemo.Behaviors
{
@ -18,7 +18,7 @@ namespace NodeEditorDemo.Behaviors
set => SetValue(RelativeToProperty, value);
}
private bool Validate(DrawingNodeViewModel drawing, object? sender, DragEventArgs e, bool bExecute)
private bool Validate(IDrawingNode drawing, object? sender, DragEventArgs e, bool bExecute)
{
var point = GetPosition(RelativeTo ?? sender, e);
@ -75,7 +75,7 @@ namespace NodeEditorDemo.Behaviors
public override bool Validate(object? sender, DragEventArgs e, object? sourceContext, object? targetContext, object? state)
{
if (targetContext is DrawingNodeViewModel drawing)
if (targetContext is IDrawingNode drawing)
{
return Validate(drawing, sender, e, false);
}
@ -85,7 +85,7 @@ namespace NodeEditorDemo.Behaviors
public override bool Execute(object? sender, DragEventArgs e, object? sourceContext, object? targetContext, object? state)
{
if (targetContext is DrawingNodeViewModel drawing)
if (targetContext is IDrawingNode drawing)
{
return Validate(drawing, sender, e, true);
}

Просмотреть файл

@ -2,13 +2,14 @@
using Avalonia.Input;
using Avalonia.VisualTree;
using Avalonia.Xaml.Interactions.DragAndDrop;
using NodeEditor.Model;
using NodeEditorDemo.ViewModels;
namespace NodeEditorDemo.Behaviors
{
public class TemplatesListBoxDropHandler : DropHandlerBase
{
private bool Validate<T>(ListBox listBox, DragEventArgs e, object? sourceContext, object? targetContext, bool bExecute) where T : NodeTemplateViewModel
private bool Validate<T>(ListBox listBox, DragEventArgs e, object? sourceContext, object? targetContext, bool bExecute) where T : INodeTemplate
{
if (sourceContext is not T sourceItem
|| targetContext is not MainWindowViewModel vm
@ -57,7 +58,7 @@ namespace NodeEditorDemo.Behaviors
{
if (e.Source is IControl && sender is ListBox listBox)
{
return Validate<NodeTemplateViewModel>(listBox, e, sourceContext, targetContext, false);
return Validate<INodeTemplate>(listBox, e, sourceContext, targetContext, false);
}
return false;
}
@ -66,7 +67,7 @@ namespace NodeEditorDemo.Behaviors
{
if (e.Source is IControl && sender is ListBox listBox)
{
return Validate<NodeTemplateViewModel>(listBox, e, sourceContext, targetContext, true);
return Validate<INodeTemplate>(listBox, e, sourceContext, targetContext, true);
}
return false;
}

Просмотреть файл

@ -21,6 +21,7 @@
<Import Project="..\..\build\Newtonsoft.Json.props" Version="13.0.1" />
<ItemGroup>
<ProjectReference Include="..\..\src\NodeEditorAvalonia.ReactiveUI\NodeEditorAvalonia.ReactiveUI.csproj" />
<ProjectReference Include="..\..\src\NodeEditorAvalonia\NodeEditorAvalonia.csproj" />
</ItemGroup>

Просмотреть файл

@ -1,6 +1,5 @@
using System;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.ReactiveUI;
namespace NodeEditorDemo

Просмотреть файл

@ -2,7 +2,6 @@ using System;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using NodeEditorDemo.ViewModels;
using ReactiveUI;
namespace NodeEditorDemo
{

Просмотреть файл

@ -8,6 +8,7 @@ using System.Windows.Input;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using NodeEditor.Model;
using NodeEditor.ViewModels;
using ReactiveUI;
@ -15,34 +16,34 @@ namespace NodeEditorDemo.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
private ObservableCollection<NodeTemplateViewModel>? _templates;
private DrawingNodeViewModel? _drawing;
private ObservableCollection<INodeTemplate>? _templates;
private IDrawingNode? _drawing;
public MainWindowViewModel()
{
_templates = new ObservableCollection<NodeTemplateViewModel>
_templates = new ObservableCollection<INodeTemplate>
{
new()
new NodeTemplateViewModel
{
Title = "Rectangle",
Build = (x, y) => NodeFactory.CreateRectangle(x, y, 60, 60, "rect")
},
new()
new NodeTemplateViewModel
{
Title = "Ellipse",
Build = (x, y) => NodeFactory.CreateEllipse(x, y, 60, 60, "ellipse")
},
new()
new NodeTemplateViewModel
{
Title = "Signal",
Build = (x, y) => NodeFactory.CreateSignal(x, y, label: "signal", state: false)
},
new()
new NodeTemplateViewModel
{
Title = "AND Gate",
Build = (x, y) => NodeFactory.CreateAndGate(x, y, 30, 30)
},
new()
new NodeTemplateViewModel
{
Title = "OR Gate",
Build = (x, y) => NodeFactory.CreateOrGate(x, y, 30, 30)
@ -107,13 +108,13 @@ namespace NodeEditorDemo.ViewModels
});
}
public ObservableCollection<NodeTemplateViewModel>? Templates
public ObservableCollection<INodeTemplate>? Templates
{
get => _templates;
set => this.RaiseAndSetIfChanged(ref _templates, value);
}
public DrawingNodeViewModel? Drawing
public IDrawingNode? Drawing
{
get => _drawing;
set => this.RaiseAndSetIfChanged(ref _drawing, value);

Просмотреть файл

@ -7,7 +7,7 @@ namespace NodeEditorDemo.ViewModels
{
public static class NodeFactory
{
public static NodeViewModel CreateRectangle(double x, double y, double width, double height, string? label, double pinSize = 8)
public static INode CreateRectangle(double x, double y, double width, double height, string? label, double pinSize = 8)
{
var node = new NodeViewModel
{
@ -15,7 +15,7 @@ namespace NodeEditorDemo.ViewModels
Y = y,
Width = width,
Height = height,
Pins = new ObservableCollection<PinViewModel>(),
Pins = new ObservableCollection<IPin>(),
Content = new RectangleViewModel { Label = label }
};
@ -27,7 +27,7 @@ namespace NodeEditorDemo.ViewModels
return node;
}
public static NodeViewModel CreateEllipse(double x, double y, double width, double height, string? label, double pinSize = 8)
public static INode CreateEllipse(double x, double y, double width, double height, string? label, double pinSize = 8)
{
var node = new NodeViewModel
{
@ -35,7 +35,7 @@ namespace NodeEditorDemo.ViewModels
Y = y,
Width = width,
Height = height,
Pins = new ObservableCollection<PinViewModel>(),
Pins = new ObservableCollection<IPin>(),
Content = new EllipseViewModel { Label = label }
};
@ -47,7 +47,7 @@ namespace NodeEditorDemo.ViewModels
return node;
}
public static NodeViewModel CreateSignal(double x, double y, double width = 120, double height = 30, string? label = null, bool? state = false, double pinSize = 8)
public static INode CreateSignal(double x, double y, double width = 120, double height = 30, string? label = null, bool? state = false, double pinSize = 8)
{
var node = new NodeViewModel
{
@ -55,7 +55,7 @@ namespace NodeEditorDemo.ViewModels
Y = y,
Width = width,
Height = height,
Pins = new ObservableCollection<PinViewModel>(),
Pins = new ObservableCollection<IPin>(),
Content = new SignalViewModel { Label = label, State = state }
};
@ -65,7 +65,7 @@ namespace NodeEditorDemo.ViewModels
return node;
}
public static NodeViewModel CreateAndGate(double x, double y, double width = 60, double height = 60, double pinSize = 8)
public static INode CreateAndGate(double x, double y, double width = 60, double height = 60, double pinSize = 8)
{
var node = new NodeViewModel
{
@ -73,7 +73,7 @@ namespace NodeEditorDemo.ViewModels
Y = y,
Width = width,
Height = height,
Pins = new ObservableCollection<PinViewModel>(),
Pins = new ObservableCollection<IPin>(),
Content = new AndGateViewModel() { Label = "&" }
};
@ -85,7 +85,7 @@ namespace NodeEditorDemo.ViewModels
return node;
}
public static NodeViewModel CreateOrGate(double x, double y, double width = 60, double height = 60, int count = 1, double pinSize = 8)
public static INode CreateOrGate(double x, double y, double width = 60, double height = 60, int count = 1, double pinSize = 8)
{
var node = new NodeViewModel
{
@ -93,7 +93,7 @@ namespace NodeEditorDemo.ViewModels
Y = y,
Width = width,
Height = height,
Pins = new ObservableCollection<PinViewModel>(),
Pins = new ObservableCollection<IPin>(),
Content = new OrGateViewModel() { Label = "≥", Count = count}
};
@ -105,7 +105,7 @@ namespace NodeEditorDemo.ViewModels
return node;
}
public static ConnectorViewModel CreateConnector(PinViewModel? start, PinViewModel? end)
public static IConnector CreateConnector(IPin? start, IPin? end)
{
return new ConnectorViewModel
{
@ -114,7 +114,7 @@ namespace NodeEditorDemo.ViewModels
};
}
public static DrawingNodeViewModel CreateDrawing()
public static IDrawingNode CreateDrawing()
{
var drawing = new DrawingNodeViewModel
{
@ -122,14 +122,14 @@ namespace NodeEditorDemo.ViewModels
Y = 0,
Width = 750,
Height = 480,
Nodes = new ObservableCollection<NodeViewModel>(),
Connectors = new ObservableCollection<ConnectorViewModel>()
Nodes = new ObservableCollection<INode>(),
Connectors = new ObservableCollection<IConnector>()
};
return drawing;
}
public static DrawingNodeViewModel CreateDemoDrawing()
public static IDrawingNode CreateDemoDrawing()
{
var drawing = new DrawingNodeViewModel
{
@ -137,66 +137,66 @@ namespace NodeEditorDemo.ViewModels
Y = 0,
Width = 750,
Height = 480,
Nodes = new ObservableCollection<NodeViewModel>(),
Connectors = new ObservableCollection<ConnectorViewModel>()
Nodes = new ObservableCollection<INode>(),
Connectors = new ObservableCollection<IConnector>()
};
var rectangle0 = NodeFactory.CreateRectangle(30, 30, 60, 60, "rect0");
var rectangle0 = CreateRectangle(30, 30, 60, 60, "rect0");
rectangle0.Parent = drawing;
drawing.Nodes.Add(rectangle0);
var rectangle1 = NodeFactory.CreateRectangle(220, 30, 60, 60, "rect1");
var rectangle1 = CreateRectangle(220, 30, 60, 60, "rect1");
rectangle1.Parent = drawing;
drawing.Nodes.Add(rectangle1);
if (rectangle0.Pins?[1] is { } && rectangle1.Pins?[0] is { })
{
var connector0 = NodeFactory.CreateConnector(rectangle0.Pins[1], rectangle1.Pins[0]);
var connector0 = CreateConnector(rectangle0.Pins[1], rectangle1.Pins[0]);
connector0.Parent = drawing;
drawing.Connectors.Add(connector0);
}
var rectangle2 = NodeFactory.CreateRectangle(30, 130, 60, 60, "rect2");
var rectangle2 = CreateRectangle(30, 130, 60, 60, "rect2");
rectangle2.Parent = drawing;
drawing.Nodes.Add(rectangle2);
var ellipse0 = NodeFactory.CreateEllipse(220, 130, 60, 60, "ellipse0");
var ellipse0 = CreateEllipse(220, 130, 60, 60, "ellipse0");
ellipse0.Parent = drawing;
drawing.Nodes.Add(ellipse0);
var signal0 = NodeFactory.CreateSignal(x: 30, y: 270, label: "in0", state: true);
var signal0 = CreateSignal(x: 30, y: 270, label: "in0", state: true);
signal0.Parent = drawing;
drawing.Nodes.Add(signal0);
var signal1 = NodeFactory.CreateSignal(x: 30, y: 390, label: "in1", state: false);
var signal1 = CreateSignal(x: 30, y: 390, label: "in1", state: false);
signal1.Parent = drawing;
drawing.Nodes.Add(signal1);
var signal2 = NodeFactory.CreateSignal(x: 360, y: 375, label: "out0", state: true);
var signal2 = CreateSignal(x: 360, y: 375, label: "out0", state: true);
signal2.Parent = drawing;
drawing.Nodes.Add(signal2);
var orGate0 = NodeFactory.CreateOrGate(240, 360);
var orGate0 = CreateOrGate(240, 360);
orGate0.Parent = drawing;
drawing.Nodes.Add(orGate0);
if (signal0.Pins?[1] is { } && orGate0.Pins?[2] is { })
{
var connector0 = NodeFactory.CreateConnector(signal0.Pins[1], orGate0.Pins[2]);
var connector0 = CreateConnector(signal0.Pins[1], orGate0.Pins[2]);
connector0.Parent = drawing;
drawing.Connectors.Add(connector0);
}
if (signal1.Pins?[1] is { } && orGate0.Pins?[0] is { })
{
var connector0 = NodeFactory.CreateConnector(signal1.Pins[1], orGate0.Pins[0]);
var connector0 = CreateConnector(signal1.Pins[1], orGate0.Pins[0]);
connector0.Parent = drawing;
drawing.Connectors.Add(connector0);
}
if (orGate0.Pins?[1] is { } && signal2.Pins?[0] is { })
{
var connector1 = NodeFactory.CreateConnector(orGate0.Pins[1], signal2.Pins[0]);
var connector1 = CreateConnector(orGate0.Pins[1], signal2.Pins[0]);
connector1.Parent = drawing;
drawing.Connectors.Add(connector1);
}

Просмотреть файл

@ -1,5 +1,11 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace NodeEditorDemo.ViewModels
{
@ -7,6 +13,30 @@ namespace NodeEditorDemo.ViewModels
{
private static readonly JsonSerializerSettings s_settings;
private class ListContractResolver : DefaultContractResolver
{
private readonly Type _type;
public ListContractResolver(Type type)
{
_type = type;
}
public override JsonContract ResolveContract(Type type)
{
if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(IList<>))
{
return base.ResolveContract(_type.MakeGenericType(type.GenericTypeArguments[0]));
}
return base.ResolveContract(type);
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
return base.CreateProperties(type, memberSerialization).Where(p => p.Writable).ToList();
}
}
static NodeSerializer()
{
s_settings = new JsonSerializerSettings()
@ -15,7 +45,8 @@ namespace NodeEditorDemo.ViewModels
TypeNameHandling = TypeNameHandling.Objects,
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
NullValueHandling = NullValueHandling.Ignore
ContractResolver = new ListContractResolver(typeof(ObservableCollection<>)),
NullValueHandling = NullValueHandling.Ignore,
};
}

Просмотреть файл

@ -1,5 +1,6 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:m="clr-namespace:NodeEditor.Model;assembly=NodeEditorAvalonia.Model"
xmlns:vm="using:NodeEditorDemo.ViewModels"
xmlns:editor="clr-namespace:NodeEditor.Controls;assembly=NodeEditorAvalonia"
xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
@ -47,7 +48,7 @@
</Style>
</ListBox.Styles>
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:NodeTemplateViewModel">
<DataTemplate DataType="m:INodeTemplate">
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</ListBox.ItemTemplate>

Просмотреть файл

@ -1,104 +1,9 @@
using System;
using System.Runtime.Serialization;
using NodeEditor.Model;
using ReactiveUI;
namespace NodeEditor.ViewModels
namespace NodeEditor.Model
{
[DataContract(IsReference = true)]
public class ConnectorViewModel : ReactiveObject
public static class ConnectorExtensions
{
private DrawingNodeViewModel? _parent;
private ConnectorOrientation _orientation;
private PinViewModel? _start;
private PinViewModel? _end;
private double _offset = 50;
public ConnectorViewModel()
{
this.WhenAnyValue(x => x.Start)
.Subscribe(start =>
{
if (start?.Parent is { })
{
start.Parent.WhenAnyValue(x => x.X).Subscribe(_ => this.RaisePropertyChanged(nameof(Start)));
start.Parent.WhenAnyValue(x => x.Y).Subscribe(_ => this.RaisePropertyChanged(nameof(Start)));
}
else
{
if (start is { })
{
start.WhenAnyValue(x => x.X).Subscribe(_ => this.RaisePropertyChanged(nameof(Start)));
start.WhenAnyValue(x => x.Y).Subscribe(_ => this.RaisePropertyChanged(nameof(Start)));
}
}
if (start is { })
{
start.WhenAnyValue(x => x.Alignment).Subscribe(_ => this.RaisePropertyChanged(nameof(Start)));
}
});
this.WhenAnyValue(x => x.End)
.Subscribe(end =>
{
if (end?.Parent is { })
{
end.Parent.WhenAnyValue(x => x.X).Subscribe(_ => this.RaisePropertyChanged(nameof(End)));
end.Parent.WhenAnyValue(x => x.Y).Subscribe(_ => this.RaisePropertyChanged(nameof(End)));
}
else
{
if (end is { })
{
end.WhenAnyValue(x => x.X).Subscribe(_ => this.RaisePropertyChanged(nameof(End)));
end.WhenAnyValue(x => x.Y).Subscribe(_ => this.RaisePropertyChanged(nameof(End)));
}
}
if (end is { })
{
end.WhenAnyValue(x => x.Alignment).Subscribe(_ => this.RaisePropertyChanged(nameof(End)));
}
});
}
[DataMember(IsRequired = true, EmitDefaultValue = true)]
public DrawingNodeViewModel? Parent
{
get => _parent;
set => this.RaiseAndSetIfChanged(ref _parent, value);
}
[DataMember(IsRequired = false, EmitDefaultValue = false)]
public ConnectorOrientation Orientation
{
get => _orientation;
set => this.RaiseAndSetIfChanged(ref _orientation, value);
}
[DataMember(IsRequired = true, EmitDefaultValue = true)]
public PinViewModel? Start
{
get => _start;
set => this.RaiseAndSetIfChanged(ref _start, value);
}
[DataMember(IsRequired = true, EmitDefaultValue = true)]
public PinViewModel? End
{
get => _end;
set => this.RaiseAndSetIfChanged(ref _end, value);
}
[DataMember(IsRequired = false, EmitDefaultValue = false)]
public double Offset
{
get => _offset;
set => this.RaiseAndSetIfChanged(ref _offset, value);
}
public void GetControlPoints(
public static void GetControlPoints(
this IConnector connector,
ConnectorOrientation orientation,
double offset,
PinAlignment p1A,

Просмотреть файл

@ -0,0 +1,11 @@
namespace NodeEditor.Model
{
public interface IConnector
{
IDrawingNode? Parent { get; set; }
ConnectorOrientation Orientation { get; set; }
IPin? Start { get; set; }
IPin? End { get; set; }
double Offset { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace NodeEditor.Model
{
public interface IDrawingNode : INode
{
IList<INode>? Nodes { get; set; }
IList<IConnector>? Connectors { get; set; }
void DrawingPressed(double x, double y);
void DrawingCancel();
void ConnectorPressed(IPin pin);
void ConnectorMove(double x, double y);
}
}

Просмотреть файл

@ -0,0 +1,15 @@
using System.Collections.Generic;
namespace NodeEditor.Model
{
public interface INode
{
INode? Parent { get; set; }
double X { get; set; }
double Y { get; set; }
double Width { get; set; }
double Height { get; set; }
object? Content { get; set; }
IList<IPin>? Pins { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,10 @@
using System;
namespace NodeEditor.Model
{
public interface INodeTemplate
{
string? Title { get; set; }
Func<double, double, INode>? Build { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,12 @@
namespace NodeEditor.Model
{
public interface IPin
{
INode? Parent { get; set; }
double X { get; set; }
double Y { get; set; }
double Width { get; set; }
double Height { get; set; }
PinAlignment Alignment { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net5.0</TargetFrameworks>
<IsPackable>True</IsPackable>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<RootNamespace>NodeEditor</RootNamespace>
</PropertyGroup>
<PropertyGroup>
<PackageId>NodeEditorAvalonia.Model</PackageId>
<Description>A node editor control for Avalonia.</Description>
<PackageTags>node;editor;nodeeditor;graph;control;xaml;axaml;avalonia;avaloniaui</PackageTags>
</PropertyGroup>
<Import Project="..\..\build\Base.props" />
<Import Project="..\..\build\SignAssembly.props" />
<Import Project="..\..\build\SourceLink.props" />
<Import Project="..\..\build\ReferenceAssemblies.props" />
</Project>

Просмотреть файл

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net5.0</TargetFrameworks>
<IsPackable>True</IsPackable>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<RootNamespace>NodeEditor</RootNamespace>
</PropertyGroup>
<PropertyGroup>
<PackageId>NodeEditorAvalonia.ReactiveUI</PackageId>
<Description>A node editor control for Avalonia.</Description>
<PackageTags>node;editor;nodeeditor;graph;control;xaml;axaml;avalonia;avaloniaui</PackageTags>
</PropertyGroup>
<Import Project="..\..\build\Base.props" />
<!--<Import Project="..\..\build\SignAssembly.props" />-->
<Import Project="..\..\build\SourceLink.props" />
<Import Project="..\..\build\ReferenceAssemblies.props" />
<Import Project="..\..\build\ReactiveUI.props" />
<ItemGroup>
<ProjectReference Include="..\NodeEditorAvalonia.Model\NodeEditorAvalonia.Model.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,101 @@
using System;
using System.Runtime.Serialization;
using NodeEditor.Model;
using ReactiveUI;
namespace NodeEditor.ViewModels
{
[DataContract(IsReference = true)]
public class ConnectorViewModel : ReactiveObject, IConnector
{
private IDrawingNode? _parent;
private ConnectorOrientation _orientation;
private IPin? _start;
private IPin? _end;
private double _offset = 50;
public ConnectorViewModel()
{
this.WhenAnyValue(x => x.Start)
.Subscribe(start =>
{
if (start?.Parent is { })
{
start.Parent.WhenAnyValue(x => x.X).Subscribe(_ => this.RaisePropertyChanged(nameof(Start)));
start.Parent.WhenAnyValue(x => x.Y).Subscribe(_ => this.RaisePropertyChanged(nameof(Start)));
}
else
{
if (start is { })
{
start.WhenAnyValue(x => x.X).Subscribe(_ => this.RaisePropertyChanged(nameof(Start)));
start.WhenAnyValue(x => x.Y).Subscribe(_ => this.RaisePropertyChanged(nameof(Start)));
}
}
if (start is { })
{
start.WhenAnyValue(x => x.Alignment).Subscribe(_ => this.RaisePropertyChanged(nameof(Start)));
}
});
this.WhenAnyValue(x => x.End)
.Subscribe(end =>
{
if (end?.Parent is { })
{
end.Parent.WhenAnyValue(x => x.X).Subscribe(_ => this.RaisePropertyChanged(nameof(End)));
end.Parent.WhenAnyValue(x => x.Y).Subscribe(_ => this.RaisePropertyChanged(nameof(End)));
}
else
{
if (end is { })
{
end.WhenAnyValue(x => x.X).Subscribe(_ => this.RaisePropertyChanged(nameof(End)));
end.WhenAnyValue(x => x.Y).Subscribe(_ => this.RaisePropertyChanged(nameof(End)));
}
}
if (end is { })
{
end.WhenAnyValue(x => x.Alignment).Subscribe(_ => this.RaisePropertyChanged(nameof(End)));
}
});
}
[DataMember(IsRequired = true, EmitDefaultValue = true)]
public IDrawingNode? Parent
{
get => _parent;
set => this.RaiseAndSetIfChanged(ref _parent, value);
}
[DataMember(IsRequired = false, EmitDefaultValue = false)]
public ConnectorOrientation Orientation
{
get => _orientation;
set => this.RaiseAndSetIfChanged(ref _orientation, value);
}
[DataMember(IsRequired = true, EmitDefaultValue = true)]
public IPin? Start
{
get => _start;
set => this.RaiseAndSetIfChanged(ref _start, value);
}
[DataMember(IsRequired = true, EmitDefaultValue = true)]
public IPin? End
{
get => _end;
set => this.RaiseAndSetIfChanged(ref _end, value);
}
[DataMember(IsRequired = false, EmitDefaultValue = false)]
public double Offset
{
get => _offset;
set => this.RaiseAndSetIfChanged(ref _offset, value);
}
}
}

Просмотреть файл

@ -1,17 +1,19 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;
using NodeEditor.Model;
using ReactiveUI;
namespace NodeEditor.ViewModels
{
[DataContract(IsReference = true)]
public class DrawingNodeViewModel : NodeViewModel
public class DrawingNodeViewModel : NodeViewModel, IDrawingNode
{
private ObservableCollection<NodeViewModel>? _nodes;
private ObservableCollection<ConnectorViewModel>? _connectors;
private IList<INode>? _nodes;
private IList<IConnector>? _connectors;
[DataMember(IsRequired = true, EmitDefaultValue = true)]
public ObservableCollection<NodeViewModel>? Nodes
public IList<INode>? Nodes
{
get => _nodes;
set => this.RaiseAndSetIfChanged(ref _nodes, value);
@ -19,40 +21,40 @@ namespace NodeEditor.ViewModels
[DataMember(IsRequired = true, EmitDefaultValue = true)]
public ObservableCollection<ConnectorViewModel>? Connectors
public IList<IConnector>? Connectors
{
get => _connectors;
set => this.RaiseAndSetIfChanged(ref _connectors, value);
}
private ConnectorViewModel? _connectorViewModel;
private IConnector? _connector;
public void DrawingPressed(double d, double d1)
public void DrawingPressed(double x, double y)
{
// TODO:
}
public void DrawingCancel()
{
if (_connectorViewModel is { })
if (_connector is { })
{
if (Connectors is { })
{
Connectors.Remove(_connectorViewModel);
Connectors.Remove(_connector);
}
_connectorViewModel = null;
_connector = null;
}
}
public void ConnectorPressed(PinViewModel pin)
public void ConnectorPressed(IPin pin)
{
if (_connectors is null)
{
return;
}
if (_connectorViewModel is null)
if (_connector is null)
{
var x = pin.X;
var y = pin.Y;
@ -79,26 +81,26 @@ namespace NodeEditor.ViewModels
End = end
};
Connectors ??= new ObservableCollection<ConnectorViewModel>();
Connectors ??= new ObservableCollection<IConnector>();
Connectors.Add(connector);
_connectorViewModel = connector;
_connector = connector;
}
else
{
_connectorViewModel.End = pin;
_connectorViewModel = null;
_connector.End = pin;
_connector = null;
}
}
public void ConnectorMove(double x, double y)
{
if (_connectorViewModel is { })
if (_connector is { })
{
if (_connectorViewModel.End is { })
if (_connector.End is { })
{
_connectorViewModel.End.X = x;
_connectorViewModel.End.Y = y;
_connector.End.X = x;
_connector.End.Y = y;
}
}
}

Просмотреть файл

@ -1,13 +1,13 @@
using System;
using NodeEditor.ViewModels;
using NodeEditor.Model;
using ReactiveUI;
namespace NodeEditorDemo.ViewModels
namespace NodeEditor.ViewModels
{
public class NodeTemplateViewModel : ViewModelBase
public class NodeTemplateViewModel : ReactiveObject, INodeTemplate
{
private string? _title;
private Func<double, double, NodeViewModel>? _build;
private Func<double, double, INode>? _build;
public string? Title
{
@ -15,7 +15,7 @@ namespace NodeEditorDemo.ViewModels
set => this.RaiseAndSetIfChanged(ref _title, value);
}
public Func<double, double, NodeViewModel>? Build
public Func<double, double, INode>? Build
{
get => _build;
set => this.RaiseAndSetIfChanged(ref _build, value);

Просмотреть файл

@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;
using NodeEditor.Model;
@ -6,18 +7,18 @@ using ReactiveUI;
namespace NodeEditor.ViewModels
{
[DataContract(IsReference = true)]
public class NodeViewModel : ReactiveObject
public class NodeViewModel : ReactiveObject, INode
{
private NodeViewModel? _parent;
private INode? _parent;
private double _x;
private double _y;
private double _width;
private double _height;
private object? _content;
private ObservableCollection<PinViewModel>? _pins;
private IList<IPin>? _pins;
[DataMember(IsRequired = false, EmitDefaultValue = false)]
public NodeViewModel? Parent
public INode? Parent
{
get => _parent;
set => this.RaiseAndSetIfChanged(ref _parent, value);
@ -59,13 +60,13 @@ namespace NodeEditor.ViewModels
}
[DataMember(IsRequired = false, EmitDefaultValue = false)]
public ObservableCollection<PinViewModel>? Pins
public IList<IPin>? Pins
{
get => _pins;
set => this.RaiseAndSetIfChanged(ref _pins, value);
}
public PinViewModel AddPin(double x, double y, double width, double height, PinAlignment alignment = PinAlignment.None)
public IPin AddPin(double x, double y, double width, double height, PinAlignment alignment = PinAlignment.None)
{
var pin = new PinViewModel()
{
@ -77,7 +78,7 @@ namespace NodeEditor.ViewModels
Alignment = alignment
};
Pins ??= new ObservableCollection<PinViewModel>();
Pins ??= new ObservableCollection<IPin>();
Pins.Add(pin);
return pin;

Просмотреть файл

@ -5,9 +5,9 @@ using ReactiveUI;
namespace NodeEditor.ViewModels
{
[DataContract(IsReference = true)]
public class PinViewModel : ReactiveObject
public class PinViewModel : ReactiveObject, IPin
{
private NodeViewModel? _parent;
private INode? _parent;
private double _x;
private double _y;
private double _width;
@ -15,7 +15,7 @@ namespace NodeEditor.ViewModels
private PinAlignment _alignment;
[DataMember(IsRequired = true, EmitDefaultValue = true)]
public NodeViewModel? Parent
public INode? Parent
{
get => _parent;
set => this.RaiseAndSetIfChanged(ref _parent, value);

Просмотреть файл

@ -2,7 +2,7 @@
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Xaml.Interactivity;
using NodeEditor.ViewModels;
using NodeEditor.Model;
namespace NodeEditor.Behaviors
{
@ -35,14 +35,14 @@ namespace NodeEditor.Behaviors
return;
}
if (AssociatedObject.DataContext is not DrawingNodeViewModel drawingNodeViewModel)
if (AssociatedObject.DataContext is not IDrawingNode drawingNode)
{
return;
}
var (x, y) = e.GetPosition(AssociatedObject);
drawingNodeViewModel.ConnectorMove(x, y);
drawingNode.ConnectorMove(x, y);
}
}
}

Просмотреть файл

@ -2,7 +2,7 @@
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Xaml.Interactivity;
using NodeEditor.ViewModels;
using NodeEditor.Model;
namespace NodeEditor.Behaviors
{
@ -35,7 +35,7 @@ namespace NodeEditor.Behaviors
return;
}
if (AssociatedObject.DataContext is not DrawingNodeViewModel drawingNodeViewModel)
if (AssociatedObject.DataContext is not IDrawingNode drawingNode)
{
return;
}
@ -44,11 +44,11 @@ namespace NodeEditor.Behaviors
if (e.GetCurrentPoint(AssociatedObject).Properties.IsLeftButtonPressed)
{
drawingNodeViewModel.DrawingPressed(x, y);
drawingNode.DrawingPressed(x, y);
}
else if (e.GetCurrentPoint(AssociatedObject).Properties.IsRightButtonPressed)
{
drawingNodeViewModel.DrawingCancel();
drawingNode.DrawingCancel();
}
}
}

Просмотреть файл

@ -6,7 +6,7 @@ using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Xaml.Interactivity;
using NodeEditor.Controls;
using NodeEditor.ViewModels;
using NodeEditor.Model;
namespace NodeEditor.Behaviors
{
@ -49,7 +49,7 @@ namespace NodeEditor.Behaviors
return;
}
if (AssociatedObject.DataContext is not DrawingNodeViewModel)
if (AssociatedObject.DataContext is not IDrawingNode)
{
return;
}
@ -77,7 +77,7 @@ namespace NodeEditor.Behaviors
if (!_dragSelectedItems)
{
if (e.Source is Control control && control.DataContext is not DrawingNodeViewModel)
if (e.Source is Control control && control.DataContext is not IDrawingNode)
{
return;
}
@ -91,21 +91,21 @@ namespace NodeEditor.Behaviors
}
}
private void Released(object sender, PointerReleasedEventArgs e)
private void Released(object? sender, PointerReleasedEventArgs e)
{
if (AssociatedObject is null)
{
return;
}
if (AssociatedObject.DataContext is not DrawingNodeViewModel)
if (AssociatedObject.DataContext is not IDrawingNode)
{
return;
}
_dragSelectedItems = false;
if (e.Source is Control control && control.DataContext is not DrawingNodeViewModel)
if (e.Source is Control control && control.DataContext is not IDrawingNode)
{
return;
}
@ -161,7 +161,7 @@ namespace NodeEditor.Behaviors
return;
}
if (AssociatedObject.DataContext is not DrawingNodeViewModel drawingNodeViewModel)
if (AssociatedObject.DataContext is not IDrawingNode)
{
return;
}
@ -180,14 +180,14 @@ namespace NodeEditor.Behaviors
foreach (var selectedControl in _selectedControls)
{
if (selectedControl.DataContext is NodeViewModel nodeViewModel)
if (selectedControl.DataContext is INode node)
{
var bounds = selectedControl.Bounds;
var x = nodeViewModel.X;
var y = nodeViewModel.Y;
nodeViewModel.X = x + deltaX;
nodeViewModel.Y = y + deltaY;
var x = node.X;
var y = node.Y;
node.X = x + deltaX;
node.Y = y + deltaY;
if (selectedRect.IsEmpty)
{
@ -207,7 +207,7 @@ namespace NodeEditor.Behaviors
}
else
{
if (e.Source is Control control && control.DataContext is not DrawingNodeViewModel)
if (e.Source is Control control && control.DataContext is not IDrawingNode)
{
return;
}

Просмотреть файл

@ -3,7 +3,7 @@ using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Xaml.Interactivity;
using NodeEditor.ViewModels;
using NodeEditor.Model;
namespace NodeEditor.Behaviors
{
@ -48,7 +48,7 @@ namespace NodeEditor.Behaviors
return;
}
if (e.Source is Control control && control.DataContext is PinViewModel)
if (e.Source is Control control && control.DataContext is IPin)
{
return;
}
@ -83,12 +83,12 @@ namespace NodeEditor.Behaviors
return;
}
if (e.Source is Control control && control.DataContext is PinViewModel)
if (e.Source is Control control && control.DataContext is IPin)
{
return;
}
if (_draggedContainer.DataContext is not NodeViewModel nodeViewModel)
if (_draggedContainer.DataContext is not INode node)
{
return;
}
@ -98,10 +98,10 @@ namespace NodeEditor.Behaviors
var deltaY = position.Y - _start.Y;
_moved = true;
_start = position;
var x = nodeViewModel.X;
var y = nodeViewModel.Y;
nodeViewModel.X = x + deltaX;
nodeViewModel.Y = y + deltaY;
var x = node.X;
var y = node.Y;
node.X = x + deltaX;
node.Y = y + deltaY;
}
private void Released()

Просмотреть файл

@ -2,7 +2,7 @@
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Xaml.Interactivity;
using NodeEditor.ViewModels;
using NodeEditor.Model;
namespace NodeEditor.Behaviors
{
@ -35,21 +35,21 @@ namespace NodeEditor.Behaviors
return;
}
if (AssociatedObject.DataContext is not PinViewModel pinViewModel)
if (AssociatedObject.DataContext is not IPin pin)
{
return;
}
if (pinViewModel.Parent is not { } nodeViewModel)
if (pin.Parent is not { } nodeViewModel)
{
return;
}
if (nodeViewModel.Parent is DrawingNodeViewModel drawingNodeViewModel)
if (nodeViewModel.Parent is IDrawingNode drawingNode)
{
if (e.GetCurrentPoint(AssociatedObject).Properties.IsLeftButtonPressed)
{
drawingNodeViewModel.ConnectorPressed(pinViewModel);
drawingNode.ConnectorPressed(pin);
e.Handled = true;
}
}

Просмотреть файл

@ -2,7 +2,6 @@
using Avalonia.Controls.Shapes;
using Avalonia.Media;
using NodeEditor.Model;
using NodeEditor.ViewModels;
namespace NodeEditor.Controls
{
@ -49,7 +48,7 @@ namespace NodeEditor.Controls
context.BeginFigure(StartPoint, false);
if (DataContext is ConnectorViewModel connectorViewModel)
if (DataContext is IConnector connectorViewModel)
{
var p1X = StartPoint.X;
var p1Y = StartPoint.Y;

Просмотреть файл

@ -1,5 +1,4 @@
using System;
using Avalonia;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Media;

Просмотреть файл

@ -2,7 +2,7 @@
using System.Globalization;
using Avalonia;
using Avalonia.Data.Converters;
using NodeEditor.ViewModels;
using NodeEditor.Model;
namespace NodeEditor.Converters
{
@ -12,7 +12,7 @@ namespace NodeEditor.Converters
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is PinViewModel pin)
if (value is IPin pin)
{
return new Thickness(-pin.Width / 2, -pin.Height / 2, 0, 0);
}
@ -25,4 +25,4 @@ namespace NodeEditor.Converters
throw new NotImplementedException();
}
}
}
}

Просмотреть файл

@ -2,7 +2,7 @@ using System;
using System.Globalization;
using Avalonia;
using Avalonia.Data.Converters;
using NodeEditor.ViewModels;
using NodeEditor.Model;
namespace NodeEditor.Converters
{
@ -12,7 +12,7 @@ namespace NodeEditor.Converters
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is PinViewModel pin)
if (value is IPin pin)
{
var x = pin.X;
var y = pin.Y;
@ -34,4 +34,4 @@ namespace NodeEditor.Converters
throw new NotImplementedException();
}
}
}
}

Просмотреть файл

@ -14,11 +14,15 @@
</PropertyGroup>
<Import Project="..\..\build\Base.props" />
<!--<Import Project="..\..\build\SignAssembly.props" />-->
<Import Project="..\..\build\SignAssembly.props" />
<Import Project="..\..\build\SourceLink.props" />
<Import Project="..\..\build\ReferenceAssemblies.props" />
<Import Project="..\..\build\Avalonia.props" />
<Import Project="..\..\build\Avalonia.ReactiveUI.props" />
<Import Project="..\..\build\Avalonia.Xaml.Behaviors.props" />
<ItemGroup>
<ProjectReference Include="..\NodeEditorAvalonia.Model\NodeEditorAvalonia.Model.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -1,6 +1,6 @@
using Avalonia.Metadata;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "NodeEditor.Behaviors")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "NodeEditor.Controls")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "NodeEditor.Converters")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "NodeEditor.ViewModels")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "NodeEditor.Views")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "NodeEditor.Themes")]

Просмотреть файл

@ -1,7 +1,6 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:NodeEditor.ViewModels"
xmlns:m="clr-namespace:NodeEditor.Model"
xmlns:m="clr-namespace:NodeEditor.Model;assembly=NodeEditorAvalonia.Model"
xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
xmlns:behaviors="clr-namespace:NodeEditor.Behaviors"
xmlns:converters="clr-namespace:NodeEditor.Converters"
@ -41,11 +40,11 @@
</Style>
<Style Selector="controls|Node">
<Setter Property="Width" Value="{Binding Width}" x:DataType="vm:NodeViewModel" />
<Setter Property="Height" Value="{Binding Height}" x:DataType="vm:NodeViewModel" />
<Setter Property="Width" Value="{Binding Width}" x:DataType="m:INode" />
<Setter Property="Height" Value="{Binding Height}" x:DataType="m:INode" />
<Setter Property="ClipToBounds" Value="False" />
<Setter Property="Template">
<ControlTemplate x:DataType="vm:NodeViewModel">
<ControlTemplate x:DataType="m:INode">
<Panel Width="{Binding Width}"
Height="{Binding Height}">
<ContentPresenter Name="PART_ContentPresenter"
@ -57,7 +56,7 @@
ClipToBounds="False"
Classes="pinPressed">
<ItemsControl.Styles>
<Style Selector="ItemsControl > ContentPresenter" x:DataType="vm:PinViewModel">
<Style Selector="ItemsControl > ContentPresenter" x:DataType="m:IPin">
<Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}" />
</Style>
@ -68,7 +67,7 @@
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:PinViewModel">
<DataTemplate DataType="m:IPin">
<controls:Pin Name="PART_Pin" />
</DataTemplate>
</ItemsControl.ItemTemplate>
@ -84,7 +83,7 @@
<Setter Property="ClipToBounds" Value="False" />
<Setter Property="ContextFlyout">
<Setter.Value>
<Flyout x:DataType="vm:ConnectorViewModel">
<Flyout x:DataType="m:IConnector">
<DockPanel>
<DockPanel DockPanel.Dock="Top">
<Label Content="Orientation:" DockPanel.Dock="Top" />
@ -110,12 +109,12 @@
</Style>
<Style Selector="controls|Pin">
<Setter Property="Width" Value="{Binding Width}" x:DataType="vm:PinViewModel" />
<Setter Property="Height" Value="{Binding Height}" x:DataType="vm:PinViewModel" />
<Setter Property="Margin" Value="{Binding Converter={x:Static converters:PinMarginConverter.Instance}}" x:DataType="vm:PinViewModel" />
<Setter Property="Width" Value="{Binding Width}" x:DataType="m:IPin" />
<Setter Property="Height" Value="{Binding Height}" x:DataType="m:IPin" />
<Setter Property="Margin" Value="{Binding Converter={x:Static converters:PinMarginConverter.Instance}}" x:DataType="m:IPin" />
<Setter Property="ClipToBounds" Value="False" />
<Setter Property="Template">
<ControlTemplate x:DataType="vm:PinViewModel">
<ControlTemplate x:DataType="m:IPin">
<Rectangle Name="PART_Pin"
Width="{Binding Width}"
Height="{Binding Height}" />
@ -123,7 +122,7 @@
</Setter>
<Setter Property="ContextFlyout">
<Setter.Value>
<Flyout x:DataType="vm:PinViewModel">
<Flyout x:DataType="m:IPin">
<DockPanel>
<Label Content="Alignment:" DockPanel.Dock="Top" />
<RadioButton Content="None"
@ -177,12 +176,12 @@
</Style>
<Style Selector="controls|DrawingNode">
<Setter Property="Width" Value="{Binding Width}" x:DataType="vm:DrawingNodeViewModel" />
<Setter Property="Height" Value="{Binding Height}" x:DataType="vm:DrawingNodeViewModel" />
<Setter Property="Width" Value="{Binding Width}" x:DataType="m:IDrawingNode" />
<Setter Property="Height" Value="{Binding Height}" x:DataType="m:IDrawingNode" />
<Setter Property="ClipToBounds" Value="False" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<ControlTemplate x:DataType="vm:DrawingNodeViewModel">
<ControlTemplate x:DataType="m:IDrawingNode">
<Panel Background="{TemplateBinding Background}">
<ItemsControl Name="PART_NodesItemsControl"
Items="{Binding Nodes}"
@ -192,7 +191,7 @@
ClipToBounds="False"
Classes="dragNode pointerEvents">
<ItemsControl.Styles>
<Style Selector="ItemsControl > ContentPresenter" x:DataType="vm:NodeViewModel">
<Style Selector="ItemsControl > ContentPresenter" x:DataType="m:INode">
<Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}" />
</Style>
@ -203,7 +202,7 @@
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:NodeViewModel">
<DataTemplate DataType="m:INode">
<controls:Node Name="PART_Node" />
</DataTemplate>
</ItemsControl.ItemTemplate>
@ -221,7 +220,7 @@
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="vm:ConnectorViewModel">
<DataTemplate DataType="m:IConnector">
<controls:Connector Name="PART_Connector"
StartPoint="{Binding Start, Converter={x:Static converters:PinToPointConverter.Instance}}"
EndPoint="{Binding End, Converter={x:Static converters:PinToPointConverter.Instance}}"