port from perforce
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
<UserControl x:Class="NodeNetwork.Toolkit.Group.AddEndpointDropPanel.AddEndpointDropPanelView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800" x:Name="Self">
|
||||
<UserControl.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
|
||||
</UserControl.Resources>
|
||||
<Grid Width="Auto" Height="40"
|
||||
Visibility="{Binding IsDropZoneVisible, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<Grid.Style>
|
||||
<Style TargetType="Grid">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Trigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetProperty="(Grid.Background).(SolidColorBrush.Color)" To="#44FFFFFF" Duration="0:0:0.1"/>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.EnterActions>
|
||||
<Trigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetProperty="(Grid.Background).(SolidColorBrush.Color)" To="Transparent" Duration="0:0:0.1"/>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.ExitActions>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Grid.Style>
|
||||
<Rectangle Stroke="#fff" StrokeThickness="2" StrokeDashArray="4 4" SnapsToDevicePixels="True"/>
|
||||
<TextBlock Text="{Binding DropHintText, ElementName=Self}" VerticalAlignment="Center" TextAlignment="Center" FontSize="14" Margin="15,5,15,5"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,54 @@
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace NodeNetwork.Toolkit.Group.AddEndpointDropPanel
|
||||
{
|
||||
public partial class AddEndpointDropPanelView : IViewFor<AddEndpointDropPanelViewModel>
|
||||
{
|
||||
#region ViewModel
|
||||
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register(nameof(ViewModel),
|
||||
typeof(AddEndpointDropPanelViewModel), typeof(AddEndpointDropPanelView), new PropertyMetadata(null));
|
||||
|
||||
public AddEndpointDropPanelViewModel ViewModel
|
||||
{
|
||||
get => (AddEndpointDropPanelViewModel)GetValue(ViewModelProperty);
|
||||
set => SetValue(ViewModelProperty, value);
|
||||
}
|
||||
|
||||
object IViewFor.ViewModel
|
||||
{
|
||||
get => ViewModel;
|
||||
set => ViewModel = (AddEndpointDropPanelViewModel)value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region DropHintText
|
||||
public static readonly DependencyProperty DropHintTextProperty = DependencyProperty.Register(nameof(DropHintText),
|
||||
typeof(string), typeof(AddEndpointDropPanelView), new PropertyMetadata(null));
|
||||
|
||||
public string DropHintText
|
||||
{
|
||||
get => (string)GetValue(DropHintTextProperty);
|
||||
set => SetValue(DropHintTextProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public AddEndpointDropPanelView()
|
||||
{
|
||||
InitializeComponent();
|
||||
DropHintText = "Drop here to create new entry";
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
this.Events().MouseLeftButtonUp
|
||||
.Select(_ => Unit.Default)
|
||||
.InvokeCommand(this, v => v.ViewModel.AddEndpointFromPendingConnection)
|
||||
.DisposeWith(d);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text;
|
||||
using DynamicData;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace NodeNetwork.Toolkit.Group.AddEndpointDropPanel
|
||||
{
|
||||
public class AddEndpointDropPanelViewModel : ReactiveObject
|
||||
{
|
||||
static AddEndpointDropPanelViewModel()
|
||||
{
|
||||
NNViewRegistrar.AddRegistration(() => new AddEndpointDropPanelView(), typeof(IViewFor<AddEndpointDropPanelViewModel>));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Take the pending connection from the super- or subnetwork, whichever is non-null,
|
||||
/// and add endpoints to NodeGroupIOBinding that match this connection.
|
||||
/// </summary>
|
||||
public ReactiveCommand<Unit, Unit> AddEndpointFromPendingConnection { get; }
|
||||
|
||||
#region IsDropZoneVisible
|
||||
public bool IsDropZoneVisible => _isDropZoneVisible.Value;
|
||||
private readonly ObservableAsPropertyHelper<bool> _isDropZoneVisible;
|
||||
#endregion
|
||||
|
||||
#region NodeGroupIOBinding
|
||||
public NodeGroupIOBinding NodeGroupIOBinding
|
||||
{
|
||||
get => _nodeGroupIoBinding;
|
||||
set => this.RaiseAndSetIfChanged(ref _nodeGroupIoBinding, value);
|
||||
}
|
||||
private NodeGroupIOBinding _nodeGroupIoBinding;
|
||||
#endregion
|
||||
|
||||
private readonly bool isOnSubnetEntrance;
|
||||
private readonly bool isOnSubnetExit;
|
||||
|
||||
public AddEndpointDropPanelViewModel(bool isOnSubnetEntrance = false, bool isOnSubnetExit = false)
|
||||
{
|
||||
this.isOnSubnetEntrance = isOnSubnetEntrance;
|
||||
this.isOnSubnetExit = isOnSubnetExit;
|
||||
|
||||
bool isOnSubnet = isOnSubnetEntrance || isOnSubnetExit;
|
||||
|
||||
AddEndpointFromPendingConnection = ReactiveCommand.Create(() =>
|
||||
{
|
||||
var network = isOnSubnet ? NodeGroupIOBinding.SubNetwork : NodeGroupIOBinding.SuperNetwork;
|
||||
var pendingConn = network.PendingConnection;
|
||||
|
||||
NodeInputViewModel input = null;
|
||||
NodeOutputViewModel output = null;
|
||||
|
||||
if (!CanCreateEndpointFromPendingConnection(pendingConn))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingConn.Input != null)
|
||||
{
|
||||
input = pendingConn.Input;
|
||||
if (isOnSubnet)
|
||||
{
|
||||
output = NodeGroupIOBinding.AddNewSubnetInlet(pendingConn.Input);
|
||||
}
|
||||
else
|
||||
{
|
||||
output = NodeGroupIOBinding.AddNewGroupNodeOutput(pendingConn.Input);
|
||||
}
|
||||
}
|
||||
else if (pendingConn.Output != null)
|
||||
{
|
||||
if (isOnSubnet)
|
||||
{
|
||||
input = NodeGroupIOBinding.AddNewSubnetOutlet(pendingConn.Output);
|
||||
}
|
||||
else
|
||||
{
|
||||
input = NodeGroupIOBinding.AddNewGroupNodeInput(pendingConn.Output);
|
||||
}
|
||||
output = pendingConn.Output;
|
||||
}
|
||||
|
||||
network.Connections.Add(network.ConnectionFactory(input, output));
|
||||
});
|
||||
|
||||
if (isOnSubnet)
|
||||
{
|
||||
this.WhenAnyValue(vm => vm.NodeGroupIOBinding.SubNetwork.PendingConnection)
|
||||
.Select(CanCreateEndpointFromPendingConnection)
|
||||
.ToProperty(this, vm => vm.IsDropZoneVisible, out _isDropZoneVisible);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.WhenAnyValue(vm => vm.NodeGroupIOBinding.SuperNetwork.PendingConnection)
|
||||
.Select(CanCreateEndpointFromPendingConnection)
|
||||
.ToProperty(this, vm => vm.IsDropZoneVisible, out _isDropZoneVisible);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private bool CanCreateEndpointFromPendingConnection(PendingConnectionViewModel conn)
|
||||
{
|
||||
if (conn == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var sourceNode = conn.Input != null ? conn.Input.Parent : conn.Output.Parent;
|
||||
|
||||
return sourceNode != NodeGroupIOBinding.GroupNode
|
||||
&& !(isOnSubnetEntrance && sourceNode == NodeGroupIOBinding.EntranceNode)
|
||||
&& !(isOnSubnetExit && sourceNode == NodeGroupIOBinding.ExitNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
91
intromat/NodeNetworkToolkit/Group/NodeGroupIOBinding.cs
Normal file
91
intromat/NodeNetworkToolkit/Group/NodeGroupIOBinding.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using NodeNetwork.ViewModels;
|
||||
|
||||
namespace NodeNetwork.Toolkit.Group
|
||||
{
|
||||
/// <summary>
|
||||
/// Facilitates connections between nodes outside and inside a group.
|
||||
/// This is performed by having inputs on the group node (in the supernet) that map to outputs on (mostly) the EntranceNode in the subnet.
|
||||
/// Likewise, outputs of the group node map to inputs on (mostly) the ExitNode in the subnet.
|
||||
/// </summary>
|
||||
public abstract class NodeGroupIOBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// Node in the parent network that represents the group.
|
||||
/// </summary>
|
||||
public NodeViewModel GroupNode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Inlet node in the subnet.
|
||||
/// Although this generally contains only outputs, this may contain inputs if their orientation is flipped.
|
||||
/// </summary>
|
||||
public NodeViewModel EntranceNode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Outlet node in the subnet.
|
||||
/// Although this generally contains only outputs, this may contain inputs if their orientation is flipped.
|
||||
/// </summary>
|
||||
public NodeViewModel ExitNode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Parent network that contains the GroupNode.
|
||||
/// </summary>
|
||||
public NetworkViewModel SuperNetwork => GroupNode.Parent;
|
||||
|
||||
/// <summary>
|
||||
/// Child network, contained in SuperNetwork, that contains the group member nodes (like the EntranceNode and ExitNode).
|
||||
/// </summary>
|
||||
public NetworkViewModel SubNetwork => ExitNode.Parent;
|
||||
|
||||
public NodeGroupIOBinding(NodeViewModel groupNode, NodeViewModel entranceNode, NodeViewModel exitNode)
|
||||
{
|
||||
GroupNode = groupNode;
|
||||
EntranceNode = entranceNode;
|
||||
ExitNode = exitNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given the output in the subnet, return the corresponding input on the groupnode in the supernet.
|
||||
/// </summary>
|
||||
public abstract NodeInputViewModel GetGroupNodeInput(NodeOutputViewModel subnetInlet);
|
||||
|
||||
/// <summary>
|
||||
/// Given the input on the group node in the supernet, return the corresponding output in the subnet.
|
||||
/// </summary>
|
||||
public abstract NodeOutputViewModel GetSubnetInlet(NodeInputViewModel entranceInput);
|
||||
|
||||
/// <summary>
|
||||
/// Given the output on the group node in the supernet, return the corresponding input in the subnet.
|
||||
/// </summary>
|
||||
public abstract NodeInputViewModel GetSubnetOutlet(NodeOutputViewModel groupNodeOutput);
|
||||
|
||||
/// <summary>
|
||||
/// Given the input in the subnet, return the corresponding output on the groupnode in the supernet.
|
||||
/// </summary>
|
||||
public abstract NodeOutputViewModel GetGroupNodeOutput(NodeInputViewModel subnetOutlet);
|
||||
|
||||
/// <summary>
|
||||
/// Create and add a new input to the group node, along with a corresponding output in the subnet (e.g. on the entrance node).
|
||||
/// </summary>
|
||||
/// <param name="candidateOutput">Output viewmodel that should match the new input on the group node.</param>
|
||||
/// <returns></returns>
|
||||
public abstract NodeInputViewModel AddNewGroupNodeInput(NodeOutputViewModel candidateOutput);
|
||||
|
||||
/// <summary>
|
||||
/// Create and add a new input to the group node, along with a corresponding output in the subnet (e.g. on the entrance node).
|
||||
/// </summary>
|
||||
/// <param name="candidateInput">Input viewmodel that should match the new output that is added to the subnet.</param>
|
||||
public abstract NodeOutputViewModel AddNewSubnetInlet(NodeInputViewModel candidateInput);
|
||||
|
||||
/// <summary>
|
||||
/// Create and add a new output to the group node, along with a corresponding input in the subnet (e.g. on the exit node).
|
||||
/// </summary>
|
||||
/// <param name="candidateInput">Input viewmodel that should match the new output on the group node.</param>
|
||||
public abstract NodeOutputViewModel AddNewGroupNodeOutput(NodeInputViewModel candidateInput);
|
||||
|
||||
/// <summary>
|
||||
/// Create and add a new output to the group node, along with a corresponding input in the subnet (e.g. on the exit node).
|
||||
/// </summary>
|
||||
/// <param name="candidateOutput">Output viewmodel that should match the new input that is added to the subnet.</param>
|
||||
public abstract NodeInputViewModel AddNewSubnetOutlet(NodeOutputViewModel candidateOutput);
|
||||
}
|
||||
}
|
||||
269
intromat/NodeNetworkToolkit/Group/NodeGrouper.cs
Normal file
269
intromat/NodeNetworkToolkit/Group/NodeGrouper.cs
Normal file
@@ -0,0 +1,269 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using DynamicData;
|
||||
using DynamicData.Alias;
|
||||
using NodeNetwork.Utilities;
|
||||
using NodeNetwork.ViewModels;
|
||||
|
||||
namespace NodeNetwork.Toolkit.Group
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to provide nesting of networks by grouping nodes.
|
||||
/// </summary>
|
||||
public class NodeGrouper
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new node that represents a group of nodes.
|
||||
/// The parameter is the subnetwork (constructed with SubNetworkFactory) that contains the group member nodes.
|
||||
/// </summary>
|
||||
public Func<NetworkViewModel, NodeViewModel> GroupNodeFactory { get; set; } = subnet => new NodeViewModel();
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a viewmodel for the subnetwork that will contain the group member nodes.
|
||||
/// </summary>
|
||||
public Func<NetworkViewModel, NetworkViewModel> SubNetworkFactory { get; set; } = parentNet => new NetworkViewModel();
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the node in the subnet that provides access to (mostly) inputs to the group
|
||||
/// </summary>
|
||||
public Func<NodeViewModel> EntranceNodeFactory { get; set; } = () => new NodeViewModel();
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the node in the subnet that provides access to (mostly) outputs of the group
|
||||
/// </summary>
|
||||
public Func<NodeViewModel> ExitNodeFactory { get; set; } = () => new NodeViewModel();
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a NodeGroupIOBinding from a group, entrance and exit node.
|
||||
/// </summary>
|
||||
public Func<NodeViewModel, NodeViewModel, NodeViewModel, NodeGroupIOBinding> IOBindingFactory { get; set; }
|
||||
|
||||
private bool CheckPropertiesValid() =>
|
||||
GroupNodeFactory != null && SubNetworkFactory != null && EntranceNodeFactory != null && ExitNodeFactory != null && IOBindingFactory != null;
|
||||
|
||||
/// <summary>
|
||||
/// Move the specified set of nodes to a new subnetwork, create a new group node that contains this subnet,
|
||||
/// restore inter- and intra-network connections.
|
||||
/// </summary>
|
||||
/// <param name="network">The parent network</param>
|
||||
/// <param name="nodesToGroup">The nodes to group</param>
|
||||
/// <returns>Returns the NodeGroupIOBinding that was constructed for this group using the IOBindingFactory.</returns>
|
||||
public NodeGroupIOBinding MergeIntoGroup(NetworkViewModel network, IEnumerable<NodeViewModel> nodesToGroup)
|
||||
{
|
||||
if (!CheckPropertiesValid())
|
||||
{
|
||||
throw new InvalidOperationException("All properties must be set before usage");
|
||||
}
|
||||
else if (network == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(network));
|
||||
}
|
||||
else if (nodesToGroup == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(nodesToGroup));
|
||||
}
|
||||
|
||||
var groupNodesSet = nodesToGroup is HashSet<NodeViewModel> set
|
||||
? set
|
||||
: new HashSet<NodeViewModel>(nodesToGroup);
|
||||
|
||||
// Check if nodesToGroup can be combined into a single group
|
||||
if (groupNodesSet.Count == 0)//[FT] allowed for now || !GraphAlgorithms.IsContinuousSubGraphSet(groupNodesSet))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create new empty group
|
||||
var subnet = SubNetworkFactory(network);
|
||||
|
||||
var groupNode = GroupNodeFactory(subnet);
|
||||
network.Nodes.Add(groupNode);
|
||||
|
||||
var groupEntranceNode = EntranceNodeFactory();
|
||||
var groupExitNode = ExitNodeFactory();
|
||||
|
||||
subnet.Nodes.AddRange(new []{groupEntranceNode, groupExitNode});
|
||||
|
||||
// Map from input on a group member node to group node input
|
||||
var groupNodeInputs = new Dictionary<NodeInputViewModel, NodeInputViewModel>();
|
||||
// Map from output on a group member node to group node output
|
||||
var groupNodeOutputs = new Dictionary<NodeOutputViewModel, NodeOutputViewModel>();
|
||||
|
||||
// Move the new nodes to appropriate positions
|
||||
groupNode.Position = new Point(
|
||||
groupNodesSet.Average(n => n.Position.X),
|
||||
groupNodesSet.Average(n => n.Position.Y)
|
||||
);
|
||||
|
||||
double yCoord = groupNodesSet.Average(n => n.Position.Y);
|
||||
groupEntranceNode.Position = new Point(
|
||||
groupNodesSet.Min(n => n.Position.X) - 100,
|
||||
yCoord
|
||||
);
|
||||
groupExitNode.Position = new Point(
|
||||
groupNodesSet.Max(n => n.Position.X) + 100,
|
||||
yCoord
|
||||
);
|
||||
|
||||
// Setup binding between entrance/exit inputs and outputs
|
||||
var ioBinding = IOBindingFactory(groupNode, groupEntranceNode, groupExitNode);
|
||||
|
||||
// Calculate set of connections to replace
|
||||
var subnetConnections = new List<ConnectionViewModel>();
|
||||
var borderInputConnections = new List<ConnectionViewModel>();
|
||||
var borderOutputConnections = new List<ConnectionViewModel>();
|
||||
foreach (var con in network.Connections.Items)
|
||||
{
|
||||
bool inputIsInSubnet = groupNodesSet.Contains(con.Input.Parent);
|
||||
bool outputIsInSubnet = groupNodesSet.Contains(con.Output.Parent);
|
||||
|
||||
if (inputIsInSubnet && outputIsInSubnet)
|
||||
{
|
||||
subnetConnections.Add(con);
|
||||
}
|
||||
else if (inputIsInSubnet)
|
||||
{
|
||||
borderInputConnections.Add(con);
|
||||
}
|
||||
else if (outputIsInSubnet)
|
||||
{
|
||||
borderOutputConnections.Add(con);
|
||||
}
|
||||
}
|
||||
|
||||
// Construct inputs/outputs into/out of the group
|
||||
foreach (var borderInCon in borderInputConnections)
|
||||
{
|
||||
if (!groupNodeInputs.ContainsKey(borderInCon.Input))
|
||||
{
|
||||
groupNodeInputs[borderInCon.Input] = ioBinding.AddNewGroupNodeInput(borderInCon.Output);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var borderOutCon in borderOutputConnections)
|
||||
{
|
||||
if (!groupNodeOutputs.ContainsKey(borderOutCon.Output))
|
||||
{
|
||||
groupNodeOutputs[borderOutCon.Output] = ioBinding.AddNewGroupNodeOutput(borderOutCon.Input);
|
||||
}
|
||||
}
|
||||
|
||||
// Transfer nodes and inner connections to subnet
|
||||
network.Connections.Edit(l =>
|
||||
{
|
||||
l.RemoveMany(subnetConnections);
|
||||
l.RemoveMany(borderInputConnections);
|
||||
l.RemoveMany(borderOutputConnections);
|
||||
});
|
||||
network.Nodes.RemoveMany(groupNodesSet);
|
||||
subnet.Nodes.AddRange(groupNodesSet);
|
||||
subnet.Connections.AddRange(subnetConnections.Select(con => subnet.ConnectionFactory(con.Input, con.Output)));
|
||||
|
||||
// Restore connections in/out of group
|
||||
network.Connections.AddRange(Enumerable.Concat(
|
||||
borderInputConnections.Select(con => network.ConnectionFactory(groupNodeInputs[con.Input], con.Output)),
|
||||
borderOutputConnections.Select(con => network.ConnectionFactory(con.Input, groupNodeOutputs[con.Output]))
|
||||
));
|
||||
subnet.Connections.AddRange(Enumerable.Concat(
|
||||
borderInputConnections.Select(con => subnet.ConnectionFactory(con.Input, ioBinding.GetSubnetInlet(groupNodeInputs[con.Input]))),
|
||||
borderOutputConnections.Select(con => subnet.ConnectionFactory(ioBinding.GetSubnetOutlet(groupNodeOutputs[con.Output]), con.Output))
|
||||
));
|
||||
|
||||
return ioBinding;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the grouping performed by MergeIntoGroup.
|
||||
/// Group members get moved back into the parent network and the group node is removed.
|
||||
/// </summary>
|
||||
/// <param name="nodeGroupInfo">The NodeGroupIOBinding of the group to dissolve.</param>
|
||||
public void Ungroup(NodeGroupIOBinding nodeGroupInfo)
|
||||
{
|
||||
if (!CheckPropertiesValid())
|
||||
{
|
||||
throw new InvalidOperationException("All properties must be set before usage");
|
||||
}
|
||||
else if (nodeGroupInfo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(nodeGroupInfo));
|
||||
}
|
||||
|
||||
var supernet = nodeGroupInfo.SuperNetwork;
|
||||
var subnet = nodeGroupInfo.SubNetwork;
|
||||
|
||||
// Calculate set of subnet connections to replace
|
||||
var borderInputConnections = new List<Tuple<NodeOutputViewModel, NodeInputViewModel[]>>();
|
||||
var borderOutputConnections = new List<Tuple<NodeInputViewModel, NodeOutputViewModel[]>>();
|
||||
var subnetConnections = new List<ConnectionViewModel>();
|
||||
foreach (var conn in subnet.Connections.Items)
|
||||
{
|
||||
if (conn.Input.Parent == nodeGroupInfo.EntranceNode || conn.Input.Parent == nodeGroupInfo.ExitNode)
|
||||
{
|
||||
var inputs = nodeGroupInfo.GetGroupNodeOutput(conn.Input).Connections.Items.Select(c => c.Input).ToArray();
|
||||
if (inputs.Length > 0)
|
||||
{
|
||||
borderInputConnections.Add(Tuple.Create(conn.Output, inputs));
|
||||
}
|
||||
}
|
||||
else if (conn.Output.Parent == nodeGroupInfo.EntranceNode || conn.Output.Parent == nodeGroupInfo.ExitNode)
|
||||
{
|
||||
var outputs = nodeGroupInfo.GetGroupNodeInput(conn.Output).Connections.Items.Select(c => c.Output).ToArray();
|
||||
if (outputs.Length > 0)
|
||||
{
|
||||
borderOutputConnections.Add(Tuple.Create(conn.Input, outputs));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
subnetConnections.Add(conn);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate set of nodes to move
|
||||
var groupMemberNodes = subnet.Nodes.Items.Where(node => node != nodeGroupInfo.EntranceNode && node != nodeGroupInfo.ExitNode).ToArray();
|
||||
|
||||
// Calculate center of nodes
|
||||
var minX = groupMemberNodes.Min(n => n.Position.X);
|
||||
var minY = groupMemberNodes.Min(n => n.Position.Y);
|
||||
var maxX = groupMemberNodes.Max(n => n.Position.X);
|
||||
var maxY = groupMemberNodes.Max(n => n.Position.Y);
|
||||
var center = new Vector(minX + (maxX - minX)/2, minY + (maxY - minY)/2);
|
||||
|
||||
// Remove connections and nodes from subnet
|
||||
subnet.Connections.Clear();
|
||||
subnet.Nodes.Clear();
|
||||
|
||||
// Remove groupnode and connections from supernet
|
||||
var groupNodePos = new Vector(nodeGroupInfo.GroupNode.Position.X, nodeGroupInfo.GroupNode.Position.Y);
|
||||
supernet.Nodes.Remove(nodeGroupInfo.GroupNode);
|
||||
|
||||
// Add nodes to supernet and move them to correct position
|
||||
supernet.Nodes.AddRange(groupMemberNodes);
|
||||
foreach (var node in groupMemberNodes)
|
||||
{
|
||||
node.Position = node.Position - center + groupNodePos;
|
||||
}
|
||||
|
||||
// Add connections to supernet
|
||||
supernet.Connections.AddRange(subnetConnections);
|
||||
foreach (var connTuple in borderInputConnections)
|
||||
{
|
||||
var output = connTuple.Item1;
|
||||
var inputs = connTuple.Item2;
|
||||
var connections = inputs.Select(input => supernet.ConnectionFactory(input, output));
|
||||
supernet.Connections.AddRange(connections);
|
||||
}
|
||||
foreach (var connTuple in borderOutputConnections)
|
||||
{
|
||||
var outputs = connTuple.Item2;
|
||||
var input = connTuple.Item1;
|
||||
var connections = outputs.Select(output => supernet.ConnectionFactory(input, output));
|
||||
supernet.Connections.AddRange(connections);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
219
intromat/NodeNetworkToolkit/Group/ValueNodeGroupIOBinding.cs
Normal file
219
intromat/NodeNetworkToolkit/Group/ValueNodeGroupIOBinding.cs
Normal file
@@ -0,0 +1,219 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using DynamicData;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace NodeNetwork.Toolkit.Group
|
||||
{
|
||||
/// <summary>
|
||||
/// Basic reference implementation of NodeGroupIOBinding for ValueInputViewModels and ValueOutputViewModels.
|
||||
/// </summary>
|
||||
public class ValueNodeGroupIOBinding : NodeGroupIOBinding
|
||||
{
|
||||
private readonly IDictionary<NodeOutputViewModel, NodeInputViewModel> _outputInputMapping = new Dictionary<NodeOutputViewModel, NodeInputViewModel>();
|
||||
|
||||
public ValueNodeGroupIOBinding(NodeViewModel groupNode, NodeViewModel entranceNode, NodeViewModel exitNode)
|
||||
: base(groupNode, entranceNode, exitNode)
|
||||
{
|
||||
// For each input on the group node, create an output in the subnet
|
||||
groupNode.Inputs.Connect()
|
||||
.Filter(input => input.PortPosition == PortPosition.Left)
|
||||
.Transform(i =>
|
||||
{
|
||||
// Dynamic is applied here so that late binding is used to find the most specific
|
||||
// CreateCompatibleOutput variant for this specific input.
|
||||
NodeOutputViewModel result = CreateCompatibleOutput((dynamic)i);
|
||||
BindOutputToInput((dynamic)result, (dynamic)i);
|
||||
return result;
|
||||
}).PopulateInto(entranceNode.Outputs);
|
||||
groupNode.Inputs.Connect()
|
||||
.Filter(input => input.PortPosition == PortPosition.Right)
|
||||
.Transform(i =>
|
||||
{
|
||||
NodeOutputViewModel result = CreateCompatibleOutput((dynamic) i);
|
||||
BindOutputToInput((dynamic) result, (dynamic) i);
|
||||
return result;
|
||||
}).PopulateInto(exitNode.Outputs);
|
||||
groupNode.Inputs.Connect().OnItemRemoved(input =>
|
||||
_outputInputMapping.Remove(
|
||||
_outputInputMapping.First(kvp => kvp.Value == input)
|
||||
)
|
||||
);
|
||||
|
||||
// For each output on the group node, create an input in the subnet
|
||||
groupNode.Outputs.Connect()
|
||||
.Filter(input => input.PortPosition == PortPosition.Right)
|
||||
.Transform(o =>
|
||||
{
|
||||
NodeInputViewModel result = CreateCompatibleInput((dynamic)o);
|
||||
BindOutputToInput((dynamic)o, (dynamic)result);
|
||||
return result;
|
||||
}).PopulateInto(exitNode.Inputs);
|
||||
groupNode.Outputs.Connect()
|
||||
.Filter(input => input.PortPosition == PortPosition.Left)
|
||||
.Transform(o =>
|
||||
{
|
||||
NodeInputViewModel result = CreateCompatibleInput((dynamic)o);
|
||||
BindOutputToInput((dynamic)o, (dynamic)result);
|
||||
return result;
|
||||
}).PopulateInto(entranceNode.Inputs);
|
||||
groupNode.Outputs.Connect().OnItemRemoved(output => _outputInputMapping.Remove(output));
|
||||
}
|
||||
|
||||
protected virtual void BindEndpointProperties(NodeOutputViewModel output, NodeInputViewModel input)
|
||||
{
|
||||
input.WhenAnyValue(vm => vm.Name).BindTo(output, vm => vm.Name);
|
||||
output.WhenAnyValue(vm => vm.Name).BindTo(input, vm => vm.Name);
|
||||
input.WhenAnyValue(vm => vm.SortIndex).BindTo(output, vm => vm.SortIndex);
|
||||
output.WhenAnyValue(vm => vm.SortIndex).BindTo(input, vm => vm.SortIndex);
|
||||
input.WhenAnyValue(vm => vm.Icon).BindTo(output, vm => vm.Icon);
|
||||
output.WhenAnyValue(vm => vm.Icon).BindTo(input, vm => vm.Icon);
|
||||
}
|
||||
|
||||
protected virtual void BindOutputToInput<T>(ValueNodeOutputViewModel<T> output, ValueNodeInputViewModel<T> input)
|
||||
{
|
||||
BindEndpointProperties(output, input);
|
||||
output.Value = input.ValueChanged;
|
||||
_outputInputMapping.Add(output, input);
|
||||
}
|
||||
|
||||
protected virtual void BindOutputToInput<T>(ValueNodeOutputViewModel<IObservableList<T>> output, ValueListNodeInputViewModel<T> input)
|
||||
{
|
||||
BindEndpointProperties(output, input);
|
||||
output.Value = Observable.Return(input.Values);
|
||||
_outputInputMapping.Add(output, input);
|
||||
}
|
||||
|
||||
#region Endpoint Create
|
||||
public virtual ValueNodeOutputViewModel<T> CreateCompatibleOutput<T>(ValueNodeInputViewModel<T> input)
|
||||
{
|
||||
return new ValueNodeOutputViewModel<T>()
|
||||
{
|
||||
Name = input.Name,
|
||||
Icon = input.Icon
|
||||
};
|
||||
}
|
||||
|
||||
public virtual ValueNodeOutputViewModel<IObservableList<T>> CreateCompatibleOutput<T>(ValueListNodeInputViewModel<T> input)
|
||||
{
|
||||
return new ValueNodeOutputViewModel<IObservableList<T>>();
|
||||
}
|
||||
|
||||
public virtual ValueNodeInputViewModel<T> CreateCompatibleInput<T>(ValueNodeOutputViewModel<T> output)
|
||||
{
|
||||
return new ValueNodeInputViewModel<T>()
|
||||
{
|
||||
Name = output.Name,
|
||||
Icon = output.Icon
|
||||
};
|
||||
}
|
||||
|
||||
public virtual ValueListNodeInputViewModel<T> CreateCompatibleInput<T>(ValueNodeOutputViewModel<IObservableList<T>> output)
|
||||
{
|
||||
return new ValueListNodeInputViewModel<T>()
|
||||
{
|
||||
Name = output.Name,
|
||||
Icon = output.Icon
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Endpoint Add
|
||||
public override NodeInputViewModel AddNewGroupNodeInput(NodeOutputViewModel candidateOutput)
|
||||
{
|
||||
NodeInputViewModel input = CreateCompatibleInput((dynamic)candidateOutput);
|
||||
input.Name = candidateOutput.Name;
|
||||
GroupNode.Inputs.Add(input);
|
||||
// Append to bottom of list
|
||||
input.SortIndex = GroupNode.Inputs.Items.Select(i => i.SortIndex).DefaultIfEmpty(-1).Max() + 1;
|
||||
return input;
|
||||
}
|
||||
|
||||
public override NodeOutputViewModel AddNewSubnetInlet(NodeInputViewModel candidateInput)
|
||||
{
|
||||
NodeInputViewModel input = AddNewGroupNodeInput(CreateCompatibleOutput((dynamic)candidateInput));
|
||||
return GetSubnetInlet(input);
|
||||
}
|
||||
|
||||
public override NodeInputViewModel AddNewSubnetOutlet(NodeOutputViewModel candidateOutput)
|
||||
{
|
||||
NodeOutputViewModel output = AddNewGroupNodeOutput(CreateCompatibleInput((dynamic)candidateOutput));
|
||||
return GetSubnetOutlet(output);
|
||||
}
|
||||
|
||||
public override NodeOutputViewModel AddNewGroupNodeOutput(NodeInputViewModel candidateInput)
|
||||
{
|
||||
NodeOutputViewModel output = CreateCompatibleOutput((dynamic)candidateInput);
|
||||
output.Name = candidateInput.Name;
|
||||
GroupNode.Outputs.Add(output);
|
||||
// Append to bottom of list
|
||||
output.SortIndex = GroupNode.Outputs.Items.Select(o => o.SortIndex).DefaultIfEmpty(-1).Max() + 1;
|
||||
return output;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Endpoint Getters
|
||||
public override NodeInputViewModel GetGroupNodeInput(NodeOutputViewModel entranceOutput)
|
||||
{
|
||||
return _outputInputMapping[entranceOutput];
|
||||
}
|
||||
|
||||
public override NodeOutputViewModel GetSubnetInlet(NodeInputViewModel entranceInput)
|
||||
{
|
||||
return _outputInputMapping.Single(p => p.Value == entranceInput).Key;
|
||||
}
|
||||
|
||||
public override NodeInputViewModel GetSubnetOutlet(NodeOutputViewModel exitOutput)
|
||||
{
|
||||
return _outputInputMapping[exitOutput];
|
||||
}
|
||||
|
||||
public override NodeOutputViewModel GetGroupNodeOutput(NodeInputViewModel exitInput)
|
||||
{
|
||||
return _outputInputMapping.Single(p => p.Value == exitInput).Key;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Remove an endpoint, which can be from group node, entrance node or exit node.
|
||||
/// Also removes the corresponding endpoint in the other network.
|
||||
/// </summary>
|
||||
/// <param name="endpoint">Input or output to be removed.</param>
|
||||
public virtual void DeleteEndpoint(Endpoint endpoint)
|
||||
{
|
||||
// Because the subnet entrance and exit are derived from the groupnode,
|
||||
// endpoints should be deleted from the groupnode only.
|
||||
|
||||
bool isOnGroupNode = GroupNode == endpoint.Parent;
|
||||
|
||||
if (endpoint is NodeInputViewModel input)
|
||||
{
|
||||
if (isOnGroupNode)
|
||||
{
|
||||
GroupNode.Inputs.Remove(input);
|
||||
}
|
||||
else
|
||||
{
|
||||
var groupOutput = GetGroupNodeOutput(input);
|
||||
GroupNode.Outputs.Remove(groupOutput);
|
||||
}
|
||||
}
|
||||
else if(endpoint is NodeOutputViewModel output)
|
||||
{
|
||||
if (isOnGroupNode)
|
||||
{
|
||||
GroupNode.Outputs.Remove(output);
|
||||
}
|
||||
else
|
||||
{
|
||||
var groupInput = GetGroupNodeInput(output);
|
||||
GroupNode.Inputs.Remove(groupInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user