Files
bluflame/intromat/NodeNetwork/ViewModels/Endpoint.cs
2026-04-18 22:31:51 +02:00

256 lines
8.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
using DynamicData;
using NodeNetwork.Utilities;
using ReactiveUI;
using Splat;
namespace NodeNetwork.ViewModels
{
/// <summary>
/// Enum type that indicates the position of the port in the endpoint
/// </summary>
public enum PortPosition
{
Left, Right
}
/// <summary>
/// Enum types that indicates the visibility behaviour of an endpoint
/// </summary>
public enum EndpointVisibility
{
/// <summary>
/// Automatically decide whether or not to show this endpoint based on the collapse status of the node
/// </summary>
Auto,
/// <summary>
/// Always show this endpoint, even if the node is collapsed
/// </summary>
AlwaysVisible,
/// <summary>
/// Always hide this endpoint
/// </summary>
AlwaysHidden
}
/// <summary>
/// Parent interface for the inputs/outputs of nodes between which connections can be made.
/// </summary>
public abstract class Endpoint : ReactiveObject
{
#region Parent
/// <summary>
/// The node that owns this endpoint
/// </summary>
public NodeViewModel Parent
{
get => _parent;
internal set => this.RaiseAndSetIfChanged(ref _parent, value);
}
private NodeViewModel _parent;
#endregion
#region Name
/// <summary>
/// The name of this endpoint.
/// In the default view, this string is displayed in the node next to the port.
/// </summary>
public string Name
{
get => _name;
set => this.RaiseAndSetIfChanged(ref _name, value);
}
private string _name = "";
#endregion
#region Group
/// <summary>
/// The group the end point belongs to. Can be null.
/// </summary>
public EndpointGroup Group
{
get => _group;
set => this.RaiseAndSetIfChanged(ref _group, value);
}
private EndpointGroup _group;
#endregion
#region Icon
/// <summary>
/// The icon displayed near the endpoint label
/// If this is null, no icon is displayed.
/// </summary>
public IBitmap Icon
{
get => _icon;
set => this.RaiseAndSetIfChanged(ref _icon, value);
}
private IBitmap _icon;
#endregion
#region Editor
/// <summary>
/// The editor viewmodel associated with this endpoint.
/// It can be used to configure the behaviour of this endpoint or provide a default value when there is no connection.
/// The editor, if not null, will be displayed in the node, under the endpoint name next to the port.
/// </summary>
public NodeEndpointEditorViewModel Editor
{
get => _editor;
set => this.RaiseAndSetIfChanged(ref _editor, value);
}
private NodeEndpointEditorViewModel _editor;
#endregion
#region Port
/// <summary>
/// The viewmodel for the port of this endpoint. (the part the user can create connections from.)
/// </summary>
public PortViewModel Port
{
get => _port;
set => this.RaiseAndSetIfChanged(ref _port, value);
}
private PortViewModel _port;
#endregion
#region PortPosition
/// <summary>
/// Where should the port be positioned in the endpoint?
/// </summary>
public PortPosition PortPosition
{
get => _portPosition;
set => this.RaiseAndSetIfChanged(ref _portPosition, value);
}
private PortPosition _portPosition;
#endregion
#region Connections
/// <summary>
/// List of connections between this endpoint and other endpoints in the network.
/// To add a new connection, do not add it here but instead add it to the Connections property in the network.
/// </summary>
public IObservableList<ConnectionViewModel> Connections { get; }
#endregion
#region MaxConnections
/// <summary>
/// The maximum amount of connections this endpoint accepts.
/// When Connections.Count == MaxConnections, the user cannot add more connections to this endpoint
/// until a connection is removed.
/// </summary>
public int MaxConnections
{
get => _maxConnections;
set => this.RaiseAndSetIfChanged(ref _maxConnections, value);
}
private int _maxConnections;
#endregion
#region Visibility
/// <summary>
/// Visibility behaviour of this endpoint
/// </summary>
public EndpointVisibility Visibility
{
get => _visibility;
set => this.RaiseAndSetIfChanged(ref _visibility, value);
}
private EndpointVisibility _visibility;
#endregion
#region SortIndex
/// <summary>
/// Inputs and outputs are sorted by increasing values of SortIndex before being displayed.
/// </summary>
public int SortIndex
{
get => _sortIndex;
set => this.RaiseAndSetIfChanged(ref _sortIndex, value);
}
private int _sortIndex;
#endregion
protected Endpoint()
{
Port = new PortViewModel();
Visibility = EndpointVisibility.Auto;
// Setup parent relationship with Port.
this.WhenAnyValue(vm => vm.Port).PairWithPreviousValue().Subscribe(p =>
{
if (p.OldValue != null)
{
p.OldValue.Parent = null;
}
if (p.NewValue != null)
{
p.NewValue.Parent = this;
}
});
// Setup Parent relationship with Editor.
this.WhenAnyValue(vm => vm.Editor).PairWithPreviousValue().Subscribe(e =>
{
if (e.OldValue != null)
{
e.OldValue.Parent = null;
}
if (e.NewValue != null)
{
e.NewValue.Parent = this;
}
});
// Mirror the port if the endpoint is on the left instead of the right.
this.WhenAnyValue(vm => vm.Port, vm => vm.PortPosition).Subscribe(_ =>
{
if (Port == null)
{
return;
}
Port.IsMirrored = PortPosition == PortPosition.Left;
});
// Setup a binding between the Connections list in the network and in this endpoint,
// selecting only the connections where this endpoint is the input or output.
// We need the latest network connections list, but we want a null value when this endpoint is
// removed from the node, or the node is removed from the network.
var networkConnections = this.WhenAnyValue(
vm => vm.Parent,
vm => vm.Parent.Parent,
vm => vm.Parent.Parent.Connections,
(x, y, z) => Parent?.Parent?.Connections ?? new SourceList<ConnectionViewModel>())
.Switch();
Connections = networkConnections
.AutoRefresh(c => c.Input)
.AutoRefresh(c => c.Output)
.Filter(c => c.Input == this || c.Output == this)
.AsObservableList();
// Setup bindings between port mouse events and connection creation.
this.WhenAnyObservable(vm => vm.Port.ConnectionDragStarted).Subscribe(_ => CreatePendingConnection());
this.WhenAnyObservable(vm => vm.Port.ConnectionPreviewActive).Subscribe(SetConnectionPreview);
this.WhenAnyObservable(vm => vm.Port.ConnectionDragFinished).Subscribe(_ => FinishPendingConnection());
}
protected abstract void CreatePendingConnection();
protected abstract void SetConnectionPreview(bool previewActive);
protected abstract void FinishPendingConnection();
}
}