Refactor editor project
This commit is contained in:
Родитель
5cb3359b04
Коммит
8bb0c7ebbc
|
@ -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
|
||||
|
|
|
@ -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}}"
|
Загрузка…
Ссылка в новой задаче