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 { /// /// Enum type that indicates the position of the port in the endpoint /// public enum PortPosition { Left, Right } /// /// Enum types that indicates the visibility behaviour of an endpoint /// public enum EndpointVisibility { /// /// Automatically decide whether or not to show this endpoint based on the collapse status of the node /// Auto, /// /// Always show this endpoint, even if the node is collapsed /// AlwaysVisible, /// /// Always hide this endpoint /// AlwaysHidden } /// /// Parent interface for the inputs/outputs of nodes between which connections can be made. /// public abstract class Endpoint : ReactiveObject { #region Parent /// /// The node that owns this endpoint /// public NodeViewModel Parent { get => _parent; internal set => this.RaiseAndSetIfChanged(ref _parent, value); } private NodeViewModel _parent; #endregion #region Name /// /// The name of this endpoint. /// In the default view, this string is displayed in the node next to the port. /// public string Name { get => _name; set => this.RaiseAndSetIfChanged(ref _name, value); } private string _name = ""; #endregion #region Group /// /// The group the end point belongs to. Can be null. /// public EndpointGroup Group { get => _group; set => this.RaiseAndSetIfChanged(ref _group, value); } private EndpointGroup _group; #endregion #region Icon /// /// The icon displayed near the endpoint label /// If this is null, no icon is displayed. /// public IBitmap Icon { get => _icon; set => this.RaiseAndSetIfChanged(ref _icon, value); } private IBitmap _icon; #endregion #region Editor /// /// 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. /// public NodeEndpointEditorViewModel Editor { get => _editor; set => this.RaiseAndSetIfChanged(ref _editor, value); } private NodeEndpointEditorViewModel _editor; #endregion #region Port /// /// The viewmodel for the port of this endpoint. (the part the user can create connections from.) /// public PortViewModel Port { get => _port; set => this.RaiseAndSetIfChanged(ref _port, value); } private PortViewModel _port; #endregion #region PortPosition /// /// Where should the port be positioned in the endpoint? /// public PortPosition PortPosition { get => _portPosition; set => this.RaiseAndSetIfChanged(ref _portPosition, value); } private PortPosition _portPosition; #endregion #region Connections /// /// 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. /// public IObservableList Connections { get; } #endregion #region MaxConnections /// /// 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. /// public int MaxConnections { get => _maxConnections; set => this.RaiseAndSetIfChanged(ref _maxConnections, value); } private int _maxConnections; #endregion #region Visibility /// /// Visibility behaviour of this endpoint /// public EndpointVisibility Visibility { get => _visibility; set => this.RaiseAndSetIfChanged(ref _visibility, value); } private EndpointVisibility _visibility; #endregion #region SortIndex /// /// Inputs and outputs are sorted by increasing values of SortIndex before being displayed. /// 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()) .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(); } }