using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using DynamicData;
using DynamicData.Binding;
using NodeNetwork.Views;
using ReactiveUI;
using Splat;
namespace NodeNetwork.ViewModels
{
public enum ResizeOrientation
{
None,
Horizontal,
Vertical,
HorizontalAndVertical
}
///
/// Viewmodel class for the nodes in the network
///
public class NodeViewModel : ReactiveObject
{
static NodeViewModel()
{
NNViewRegistrar.AddRegistration(() => new NodeView(), typeof(IViewFor));
Locator.CurrentMutable.RegisterPlatformBitmapLoader();
}
#region Logger
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
#endregion
#region Parent
///
/// The network that contains this node
///
public NetworkViewModel Parent
{
get => _parent;
internal set => this.RaiseAndSetIfChanged(ref _parent, value);
}
private NetworkViewModel _parent;
#endregion
#region Name
///
/// The name of the node.
/// In the default view, this string is displayed at the top of the node.
///
public string Name
{
get => _name;
set => this.RaiseAndSetIfChanged(ref _name, value);
}
private string _name;
#endregion
#region HeaderIcon
///
/// The icon displayed in the header of the node.
/// If this is null, no icon is displayed.
/// In the default view, this icon is displayed at the top of the node.
///
public IBitmap HeaderIcon
{
get => _headerIcon;
set => this.RaiseAndSetIfChanged(ref _headerIcon, value);
}
private IBitmap _headerIcon;
#endregion
#region Inputs
///
/// The list of inputs on this node.
///
public ISourceList Inputs { get; } = new SourceList();
#endregion
#region Outputs
///
/// The list of outputs on this node.
///
public ISourceList Outputs { get; } = new SourceList();
#endregion
#region VisibleInputs
///
/// The list of inputs that is currently visible on this node.
/// Some inputs may be hidden if the node is collapsed.
///
public IObservableList VisibleInputs { get; }
#endregion
#region VisibleOutputs
///
/// The list of outputs that is currently visible on this node.
/// Some outputs may be hidden if the node is collapsed.
///
public IObservableList VisibleOutputs { get; }
#endregion
#region VisibleEndpointGroups
///
/// The list of endpoint groups that is currently visible on this node.
/// Some groups may be hidden if the node is collapsed.
///
public ReadOnlyObservableCollection VisibleEndpointGroups { get; }
#endregion
#region EndpointGroupViewModelFactory
///
/// The function that is used to create endpoint group view models.
/// By default, this function creates a EndpointGroupViewModel.
///
public EndpointGroupViewModelFactory EndpointGroupViewModelFactory
{
get => _endpointGroupViewModelFactory;
set => this.RaiseAndSetIfChanged(ref _endpointGroupViewModelFactory, value);
}
private EndpointGroupViewModelFactory _endpointGroupViewModelFactory;
#endregion
#region IsSelected
///
/// If true, this node is currently selected in the UI.
///
public bool IsSelected
{
get => _isSelected;
set => this.RaiseAndSetIfChanged(ref _isSelected, value);
}
private bool _isSelected;
#endregion
#region IsCollapsed
///
/// If true, this node is currently collapsed.
/// If the node is collapsed, some parts of the node are hidden to provide a more compact view.
///
public bool IsCollapsed
{
get => _isCollapsed;
set => this.RaiseAndSetIfChanged(ref _isCollapsed, value);
}
private bool _isCollapsed;
#endregion
#region CanBeRemovedByUser
///
/// If true, the user can delete this node from the network in the UI.
/// True by default.
///
public bool CanBeRemovedByUser
{
get => _canBeRemovedByUser;
set => this.RaiseAndSetIfChanged(ref _canBeRemovedByUser, value);
}
private bool _canBeRemovedByUser;
#endregion
#region Position
///
/// The position of this node in the network.
///
public Point Position
{
get => _position;
set => this.RaiseAndSetIfChanged(ref _position, value);
}
private Point _position;
#endregion
#region Size
///
/// The rendered size of this node.
///
public Size Size
{
get => _size;
internal set => this.RaiseAndSetIfChanged(ref _size, value);
}
private Size _size;
#endregion
#region Resizable
///
/// On which axes can the user resize the node?
///
public ResizeOrientation Resizable
{
get => _resizable;
set => this.RaiseAndSetIfChanged(ref _resizable, value);
}
private ResizeOrientation _resizable;
#endregion
public NodeViewModel()
{
// Setup a default EndpointGroupViewModelFactory that will be used to create endpoint groups.
EndpointGroupViewModelFactory = (group, allInputs, allOutputs, children, factory) => new EndpointGroupViewModel(group, allInputs, allOutputs, children, factory);
this.Name = "Untitled";
this.CanBeRemovedByUser = true;
this.Resizable = ResizeOrientation.Horizontal;
// Setup parent relationship with inputs.
Inputs.Connect().ActOnEveryObject(
addedInput => addedInput.Parent = this,
removedInput => removedInput.Parent = null
);
// Setup parent relationship with outputs.
Outputs.Connect().ActOnEveryObject(
addedOutput => addedOutput.Parent = this,
removedOutput => removedOutput.Parent = null
);
// When an input is removed, delete any connection to/from that input
Inputs.Preview().OnItemRemoved(removedInput =>
{
if (Parent != null)
{
Parent.Connections.RemoveMany(removedInput.Connections.Items);
bool pendingConnectionInvalid = Parent.PendingConnection?.Input == removedInput;
if (pendingConnectionInvalid)
{
Parent.RemovePendingConnection();
}
}
}).Subscribe();
// Same for outputs.
Outputs.Preview().OnItemRemoved(removedOutput =>
{
if (Parent != null)
{
Parent.Connections.RemoveMany(removedOutput.Connections.Items);
bool pendingConnectionInvalid = Parent.PendingConnection?.Output == removedOutput;
if (pendingConnectionInvalid)
{
Parent.RemovePendingConnection();
}
}
}).Subscribe();
// If collapsed, hide inputs without connections, otherwise show all.
var onCollapseChange = this.WhenAnyValue(vm => vm.IsCollapsed).Publish();
onCollapseChange.Connect();
var visibilityFilteredInputs = Inputs.Connect()
.AutoRefreshOnObservable(_ => onCollapseChange)
.AutoRefresh(vm => vm.Visibility)
.AutoRefresh(vm => vm.Group)
.Filter(i =>
{
if (IsCollapsed)
{
return i.Visibility == EndpointVisibility.AlwaysVisible || (i.Visibility == EndpointVisibility.Auto && i.Connections.Items.Any());
}
return i.Visibility != EndpointVisibility.AlwaysHidden;
});
VisibleInputs = visibilityFilteredInputs
.Filter(i => i.Group == null)
.Sort(Comparer.Create((i1, i2) => i1.SortIndex.CompareTo(i2.SortIndex)),
resort: Inputs.Connect().WhenValueChanged(i => i.SortIndex).Select(_ => Unit.Default))
.AsObservableList();
// Same for outputs.
var visibilityFilteredOutputs = Outputs.Connect()
.AutoRefreshOnObservable(_ => onCollapseChange)
.AutoRefresh(vm => vm.Visibility)
.AutoRefresh(vm => vm.Group)
.Filter(o =>
{
if (IsCollapsed)
{
return o.Visibility == EndpointVisibility.AlwaysVisible || (o.Visibility == EndpointVisibility.Auto && o.Connections.Items.Any());
}
return o.Visibility != EndpointVisibility.AlwaysHidden;
});
VisibleOutputs = visibilityFilteredOutputs
.Filter(o => o.Group == null)
.Sort(Comparer.Create((o1, o2) => o1.SortIndex.CompareTo(o2.SortIndex)),
resort: Outputs.Connect().WhenValueChanged(o => o.SortIndex).Select(_ => Unit.Default))
.AsObservableList();
// Get all the groups, also the empty ones.
var allInputGroups
= visibilityFilteredInputs
.TransformMany(GetAllGroupsInHierarchy)
.AddKey(g => g);
var allOutputGroups
= visibilityFilteredOutputs
.TransformMany(GetAllGroupsInHierarchy)
.AddKey(g => g);
IEnumerable GetAllGroupsInHierarchy(Endpoint endpoint)
{
var group = endpoint.Group;
while (group != null)
{
yield return group;
group = group.Parent;
}
}
// Merge needs AddKey first, otherwise removal of endpoints leads to confusion.
var allGroups
= allInputGroups
.Merge(allOutputGroups)
.DistinctValues(g => g);
// Used as temporary root for TransformToTree.
var root = new EndpointGroup();
// To react on change of the EndpointGroupViewModelFactory.
var onEndpointGroupViewModelFactoryChange = this.WhenAnyValue(vm => vm.EndpointGroupViewModelFactory);
allGroups
.TransformToTree(group => group.Parent ?? root)
.AutoRefreshOnObservable(_ => onEndpointGroupViewModelFactoryChange)
.Transform(n => EndpointGroupViewModelFactory(n.Key,
visibilityFilteredInputs,
visibilityFilteredOutputs,
n.Children,
EndpointGroupViewModelFactory))
.Bind(out var groups)
.Subscribe();
VisibleEndpointGroups = groups;
}
}
}