added connections + buggy indexed node logic

This commit is contained in:
Asval 2024-11-25 22:13:59 +01:00
parent eeb11321f7
commit 9d7a7661d4
8 changed files with 239 additions and 33 deletions

View File

@ -4,18 +4,18 @@ namespace FModel.ViewModels.Nodify;
public class ConnectionViewModel : ViewModel
{
private ConnectorViewModel _input;
public ConnectorViewModel Input
private ConnectorViewModel _source;
public ConnectorViewModel Source
{
get => _input;
set => SetProperty(ref _input, value);
get => _source;
set => SetProperty(ref _source, value);
}
private ConnectorViewModel _output;
public ConnectorViewModel Output
private ConnectorViewModel _target;
public ConnectorViewModel Target
{
get => _output;
set => SetProperty(ref _output, value);
get => _target;
set => SetProperty(ref _target, value);
}
private bool _isSelected;
@ -25,8 +25,27 @@ public class ConnectionViewModel : ViewModel
set => SetProperty(ref _isSelected, value);
}
public ConnectionViewModel()
public ConnectionViewModel(ConnectorViewModel connector)
{
switch (connector.Flow)
{
case ConnectorFlow.Input:
Target = connector;
break;
case ConnectorFlow.Output:
Source = connector;
break;
}
}
public ConnectionViewModel(ConnectorViewModel source, ConnectorViewModel target)
{
Source = source;
Target = target;
}
public override string ToString()
{
return $"{Source.Title} -> {Target.Title}";
}
}

View File

@ -1,4 +1,5 @@
using System.Windows;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Assets.Objects.Properties;
using FModel.Framework;
@ -54,7 +55,40 @@ public class ConnectorViewModel : ViewModel
public ConnectorViewModel()
{
Connections.WhenAdded(c => c.Input = this);
void Remove(ConnectionViewModel connection)
{
if (Flow is not ConnectorFlow.Input) return;
Node.Graph.Connections.Remove(connection);
}
Connections.WhenAdded(c =>
{
switch (Flow)
{
case ConnectorFlow.Input when c.Target is null:
c.Target = this;
c.Source.Connections.Add(c);
break;
case ConnectorFlow.Output when c.Source is null:
c.Source = this;
c.Target.Connections.Add(c);
// only connect outputs to inputs
Node.Graph.Connections.Add(c);
break;
}
c.Source.IsConnected = true;
c.Target.IsConnected = true;
})
.WhenRemoved(Remove)
.WhenCleared(c =>
{
foreach (var connection in c)
{
Remove(connection);
}
});
}
public ConnectorViewModel(string title) : this()
@ -62,8 +96,13 @@ public class ConnectorViewModel : ViewModel
Title = title;
}
public ConnectorViewModel(FPropertyTagType? tag) : this(tag?.ToString())
public ConnectorViewModel(FPropertyTagData? type) : this(type?.ToString())
{
}
public override string ToString()
{
return $"{Title} ({Connections.Count} connection{(Connections.Count > 0 ? "s" : "")})";
}
}

View File

@ -1,7 +1,7 @@
using System.Windows.Controls;
using System.Collections.Generic;
using System.Windows.Controls;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Assets.Objects.Properties;
using CUE4Parse.UE4.Objects.Core.i18N;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Framework;
@ -21,40 +21,84 @@ public class FlowNodeViewModel : NodeViewModel
public FlowNodeViewModel()
{
void Clear(IList<ConnectorViewModel> connectors)
{
foreach (var connector in connectors)
{
connector.Connections.Clear();
}
}
Input.WhenAdded(c =>
{
c.Flow = ConnectorFlow.Input;
c.Node = this;
});
}).WhenCleared(Clear);
Output.WhenAdded(c =>
{
c.Flow = ConnectorFlow.Output;
c.Node = this;
});
}).WhenCleared(Clear);
Orientation = Orientation.Horizontal;
}
protected void ConnectOutput(ConnectorViewModel output, FPropertyTagType? tag)
public FlowNodeViewModel(string title) : this()
{
Title = title;
}
protected void ConnectOutput(FPropertyTagType? tag)
{
var bSameNode = this is IndexedNodeViewModel { Output.Count: 1 };
FlowNodeViewModel node = null;
switch (tag)
{
case FPropertyTagType<FScriptStruct> { Value: not null } s:
switch (s.Value.StructType)
{
case FStructFallback fallback:
node = bSameNode ? this : new FlowNodeViewModel("Properties");
foreach (var property in fallback.Properties)
{
node.Input.Add(new ConnectorViewModel(property.Name.Text));
node.Output.Add(new ConnectorViewModel(property.TagData));
node.ConnectOutput(property.Tag);
}
break;
}
break;
case FPropertyTagType<UScriptArray> { Value.Properties.Count: > 0 } a:
node = new IndexedNodeViewModel(a.Value.Properties, this) { Title = "Properties" };
node.Input.Add(new ConnectorViewModel("In"));
node.Output.Add(new ConnectorViewModel(a.Value.InnerTagData));
break;
case FPropertyTagType<FPackageIndex> { Value: not null } p:
case FPropertyTagType<FPackageIndex> { Value.IsNull: false } p:
node = bSameNode ? this : new FlowNodeViewModel("Properties");
node.Input.Add(new ConnectorViewModel("In"));
node.Output.Add(new ConnectorViewModel(p.Value.Index.ToString()));
break;
case FPropertyTagType<FSoftObjectPath> s:
node = bSameNode ? this : new FlowNodeViewModel("Properties");
node.Input.Add(new ConnectorViewModel("In"));
node.Output.Add(new ConnectorViewModel(s.Value.ToString()));
break;
case { } t:
output.Title = t.GenericValue?.ToString();
if (Output.Count == 0) Output.Add(new ConnectorViewModel());
Output[^1].Title = t.GenericValue?.ToString();
break;
}
node?.PostConnectOutput(this);
}
protected virtual void PostConnectOutput(FlowNodeViewModel parent)
{
parent.Children.Add(this);
if (Input.Count > 0 && parent.Output.Count > 0)
{
parent.Output[^1].Connections.Add(new ConnectionViewModel(Input[^1]));
}
}
}

View File

@ -1,16 +1,18 @@
using System.Collections.Generic;
using CUE4Parse.UE4.Assets.Objects;
using CUE4Parse.UE4.Assets.Objects.Properties;
using FModel.Framework;
namespace FModel.ViewModels.Nodify;
public class IndexedNodeViewModel : BaseIndexedNodeViewModel
public class IndexedNodeViewModel(List<FPropertyTagType> properties, FlowNodeViewModel parent) : BaseIndexedNodeViewModel(properties.Count)
{
public IndexedNodeViewModel(List<FPropertyTagType> properties) : base(properties.Count) { }
protected override void OnArrayIndexChanged()
{
for (int i = 1; i < Input.Count; i++) Input.RemoveAt(i);
for (int i = 1; i < Output.Count; i++) Output.RemoveAt(i);
ConnectOutput(properties[ArrayIndex]);
}
}
@ -22,14 +24,14 @@ public abstract class BaseIndexedNodeViewModel : FlowNodeViewModel
get => _arrayIndex;
set
{
if (!SetProperty(ref _arrayIndex, value)) return;
if (CanUpdateArrayIndex(value) && !SetProperty(ref _arrayIndex, value))
return;
RaisePropertyChanged(nameof(DisplayIndex));
NextCommand.RaiseCanExecuteChanged();
PreviousCommand.RaiseCanExecuteChanged();
Input.Clear();
Output.Clear();
Children.Clear();
OnArrayIndexChanged();
}
}
@ -44,10 +46,25 @@ public abstract class BaseIndexedNodeViewModel : FlowNodeViewModel
PreviousCommand = new DelegateCommand(() => ArrayIndex--, () => ArrayIndex > 0);
ArrayMax = arrayMax;
}
public void Initialize()
{
ArrayIndex = 0;
}
protected abstract void OnArrayIndexChanged();
protected override void PostConnectOutput(FlowNodeViewModel parent)
{
if (Output.Count <= 1)
{
base.PostConnectOutput(parent);
Initialize();
}
}
protected bool CanUpdateArrayIndex(int value) => value >= 0 && value < ArrayMax;
public int DisplayIndex => ArrayIndex + 1;
}

View File

@ -17,8 +17,23 @@ public abstract class NodeViewModel : ViewModel
public Orientation Orientation { get; protected set; }
public NodifyObservableCollection<NodeViewModel> Children { get; } = new();
protected NodeViewModel()
{
void Remove(NodeViewModel node)
{
Graph.Nodes.Remove(node);
}
Children.WhenAdded(c => Graph.Nodes.Add(c));
Children.WhenRemoved(Remove);
Children.WhenCleared(c =>
{
foreach (var child in c)
{
Remove(child);
}
});
}
}

View File

@ -11,6 +11,14 @@ public class NodifyEditorViewModel : ViewModel
{
Nodes
.WhenAdded(n => n.Graph = this)
.WhenRemoved(n =>
{
if (n is not FlowNodeViewModel flowNode) return;
foreach (var input in flowNode.Input)
{
input.Connections.Clear();
}
})
.WhenCleared(_ => Connections.Clear());
}
@ -18,6 +26,7 @@ public class NodifyEditorViewModel : ViewModel
{
var root = new PackagedNodeViewModel(package);
Nodes.Add(root);
root.Initialize();
}
// private void AddToCollection(NodeViewModel parent, NodeViewModel node)

View File

@ -6,14 +6,17 @@ public class PackagedNodeViewModel(IPackage package) : BaseIndexedNodeViewModel(
{
protected override void OnArrayIndexChanged()
{
Input.Clear();
Output.Clear();
var export = package.ExportsLazy[ArrayIndex].Value;
Title = export.Name;
foreach (var property in export.Properties)
{
Input.Add(new ConnectorViewModel(property.Name.Text));
Output.Add(new ConnectorViewModel(property.TagData?.ToString()));
ConnectOutput(Output[^1], property.Tag);
Output.Add(new ConnectorViewModel(property.TagData));
ConnectOutput(property.Tag);
}
}
}

View File

@ -39,6 +39,66 @@
<SolidColorBrush x:Key="SquareConnectorOutline" Color="MediumSlateBlue" Opacity="0.15" />
<SolidColorBrush x:Key="TriangleConnectorOutline" Color="MediumVioletRed" Opacity="0.15" />
<UIElement x:Key="ConnectionAnimationPlaceholder" Opacity="1" />
<Storyboard x:Key="HighlightConnection">
<DoubleAnimation Storyboard.Target="{StaticResource ConnectionAnimationPlaceholder}"
Storyboard.TargetProperty="(UIElement.Opacity)"
Duration="0:0:0.1" From="1" To="0.3" />
</Storyboard>
<Style x:Key="ConnectionStyle"
TargetType="{x:Type nodify:BaseConnection}"
BasedOn="{StaticResource {x:Type nodify:BaseConnection}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Target.Shape}" Value="{x:Static viewModels:ConnectorShape.Square}">
<Setter Property="Stroke" Value="{StaticResource SquareConnectorColor}" />
<Setter Property="Fill" Value="{StaticResource SquareConnectorColor}" />
<Setter Property="OutlineBrush" Value="{StaticResource SquareConnectorOutline}" />
</DataTrigger>
<DataTrigger Binding="{Binding Target.Shape}" Value="{x:Static viewModels:ConnectorShape.Triangle}">
<Setter Property="Stroke" Value="{StaticResource TriangleConnectorColor}" />
<Setter Property="Fill" Value="{StaticResource TriangleConnectorColor}" />
<Setter Property="OutlineBrush" Value="{StaticResource TriangleConnectorOutline}" />
</DataTrigger>
<Trigger Property="IsMouseDirectlyOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard Name="HighlightConnection" Storyboard="{StaticResource HighlightConnection}" />
</Trigger.EnterActions>
<Trigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="HighlightConnection" />
</Trigger.ExitActions>
<Setter Property="Opacity" Value="1" />
</Trigger>
<Trigger Property="IsSelectable" Value="True">
<Setter Property="Cursor" Value="Hand" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseDirectlyOver" Value="False" />
<Condition Property="IsSelected" Value="False" />
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="OutlineBrush" Value="Transparent" />
</MultiTrigger.Setters>
</MultiTrigger>
</Style.Triggers>
<Setter Property="Opacity" Value="{Binding Source={StaticResource ConnectionAnimationPlaceholder}, Path=Opacity}" />
<Setter Property="Source" Value="{Binding Source.Anchor}" />
<Setter Property="Target" Value="{Binding Target.Anchor}" />
<Setter Property="SourceOrientation" Value="{Binding Source.Node.Orientation}" />
<Setter Property="TargetOrientation" Value="{Binding Target.Node.Orientation}" />
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
<DataTemplate x:Key="LineConnectionTemplate">
<nodify:LineConnection Style="{StaticResource ConnectionStyle}" />
</DataTemplate>
<DataTemplate x:Key="ConnectionTemplate">
<nodify:Connection Style="{StaticResource ConnectionStyle}" />
</DataTemplate>
<ControlTemplate x:Key="SquareConnector" TargetType="Control">
<Rectangle Width="14"
Height="14"
@ -78,6 +138,13 @@
ViewportZoom="{Binding ViewportZoom, Mode=OneWayToSource}"
Background="{StaticResource SmallGridLinesDrawingBrush}"
ItemTemplateSelector="{DynamicResource NodeTemplateSelector}">
<nodify:NodifyEditor.Style>
<Style TargetType="{x:Type nodify:NodifyEditor}"
BasedOn="{StaticResource {x:Type nodify:NodifyEditor}}">
<Setter Property="ConnectionTemplate" Value="{StaticResource LineConnectionTemplate}" />
</Style>
</nodify:NodifyEditor.Style>
<nodify:NodifyEditor.Resources>
<Style TargetType="{x:Type nodify:Connector}"
BasedOn="{StaticResource {x:Type nodify:Connector}}">
@ -190,13 +257,6 @@
</Style.Triggers>
</Style>
</nodify:NodifyEditor.ItemContainerStyle>
<!-- <nodify:NodifyEditor.ConnectionTemplate> -->
<!-- <DataTemplate DataType="{x:Type viewModels:ConnectionViewModel}"> -->
<!-- <nodify:LineConnection Source="{Binding Input.Anchor}" -->
<!-- Target="{Binding Output.Anchor}" /> -->
<!-- </DataTemplate> -->
<!-- </nodify:NodifyEditor.ConnectionTemplate> -->
</nodify:NodifyEditor>
<Grid Background="{StaticResource LargeGridLinesDrawingBrush}" Panel.ZIndex="-2" />