using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Text; using System.Threading.Tasks; using NodeNetwork.Views; using ReactiveUI; using System.Reactive.Linq; using DynamicData; namespace NodeNetwork.ViewModels { /// /// Viewmodel class for inputs on a node. /// Inputs are endpoints that can only be connected to outputs. /// public class NodeInputViewModel : Endpoint { static NodeInputViewModel() { NNViewRegistrar.AddRegistration(() => new NodeInputView(), typeof(IViewFor)); } #region Logger private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); #endregion #region IsEditorVisible /// /// If true, the editor is visible. Otherwise, the editor is hidden. /// See HideEditorIfConnected. /// public bool IsEditorVisible => _isEditorVisible.Value; private ObservableAsPropertyHelper _isEditorVisible; #endregion #region HideEditorIfConnected /// /// If true, the editor of this input will be hidden if Connection is not null. /// This makes sense if the editor is used to provide a value when no connection is present. /// public bool HideEditorIfConnected { get => _hideEditorIfConnected; set => this.RaiseAndSetIfChanged(ref _hideEditorIfConnected, value); } private bool _hideEditorIfConnected; #endregion #region ConnectionValidator /// /// This function is called when a new connection with this input is pending. /// It decides whether or not the pending connection is valid. /// If the validation result says the pending connection is invalid, /// then the user will not be able to add the connection to the network. /// public Func ConnectionValidator { get => _connectionValidator; set => this.RaiseAndSetIfChanged(ref _connectionValidator, value); } private Func _connectionValidator; #endregion public NodeInputViewModel() { this.HideEditorIfConnected = true; this.Connections.CountChanged.Select(c => c == 0).StartWith(true) .CombineLatest(this.WhenAnyValue(vm => vm.HideEditorIfConnected), (noConnections, hideEditorIfConnected) => !hideEditorIfConnected || noConnections) .ToProperty(this, vm => vm.IsEditorVisible, out _isEditorVisible); this.ConnectionValidator = con => new ConnectionValidationResult(true, null); this.MaxConnections = 1; this.PortPosition = PortPosition.Left; } /// /// Sets the pending connection in the network to a new connection with this endpoint as the input. /// If this input already is connected, and MaxConnections == 1, /// then the connection is replaced by a pending connection without this endpoint. /// If the connection would be invalid, no pending connection is made. /// Called when the user clicks on this endpoint. /// protected override void CreatePendingConnection() { NetworkViewModel network = Parent?.Parent; if (network == null) { return; } PendingConnectionViewModel pendingConnection; if (MaxConnections == 1 && Connections.Items.Any()) { var conn = Connections.Items.First(); pendingConnection = new PendingConnectionViewModel(network) { Output = conn.Output, OutputIsLocked = true, LooseEndPoint = Port.CenterPoint }; network.Connections.Remove(conn); } else if(Connections.Count < MaxConnections) { pendingConnection = new PendingConnectionViewModel(network) { Input = this, InputIsLocked = true, LooseEndPoint = Port.CenterPoint }; } else { return; } pendingConnection.LooseEndPoint = Port.CenterPoint; network.PendingConnection = pendingConnection; } /// /// Sets this endpoint as the input of the pending connection and updates its validation. /// Called when the user drags and holds a pending connection over this endpoint. /// /// /// True to set this endpoint as the output of the pending connection. /// To remove this endpoint from the pending connection, set this to false. /// protected override void SetConnectionPreview(bool previewActive) { PendingConnectionViewModel pendingCon = Parent.Parent.PendingConnection; if (pendingCon.Input != null && (pendingCon.Input != this || pendingCon.InputIsLocked)) { return; } if (previewActive) { pendingCon.Input = this; pendingCon.Validation = ConnectionValidator(pendingCon); } else { pendingCon.Input = null; pendingCon.Validation = null; } } /// /// Tries to create a new connection in the network based on the pending connection and this endpoint as the input. /// If the connection would be invalid, no connection is made. /// The pending connection is deleted. /// Called when the user drags and releases a pending connection over this endpoint. /// protected override void FinishPendingConnection() { NetworkViewModel network = Parent?.Parent; if (network == null) { return; } if (network.PendingConnection.Input == this && !network.PendingConnection.InputIsLocked) { //Only allow drag from output to input, not input to input if (network.PendingConnection.Input.Parent != network.PendingConnection.Output.Parent) { //Dont allow connections between an input and an output on the same node if (network.PendingConnection.Validation.IsValid) { //Don't allow a new connection if max amount of connections has been reached and we //can't automatically remove one. if (Connections.Count < MaxConnections || MaxConnections == 1) { //Connection is valid bool canCreateConnection = true; if (MaxConnections == Connections.Count && MaxConnections == 1) { //Remove the connection to this input network.Connections.Remove(Connections.Items.First()); } else if (MaxConnections > 2) { // Make sure connection does not exist already. if (network.Connections.Items.Any(con => con.Output == network.PendingConnection.Output && con.Input == this)) { canCreateConnection = false; } } if (canCreateConnection) { //Add new connection network.Connections.Add(network.ConnectionFactory(this, network.PendingConnection.Output)); } } } } } network.RemovePendingConnection(); } } }