diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj
index dbae3fed..4cee2393 100644
--- a/FModel/FModel.csproj
+++ b/FModel/FModel.csproj
@@ -156,6 +156,7 @@
+
diff --git a/FModel/Framework/Command.cs b/FModel/Framework/Command.cs
index ed4b1d61..329eb404 100644
--- a/FModel/Framework/Command.cs
+++ b/FModel/Framework/Command.cs
@@ -15,4 +15,19 @@ public abstract class Command : ICommand
}
public event EventHandler CanExecuteChanged;
-}
\ No newline at end of file
+}
+
+public class DelegateCommand : Command
+{
+ private readonly Action _action;
+ private readonly Func? _condition;
+
+ public DelegateCommand(Action action, Func? executeCondition = default)
+ {
+ _action = action ?? throw new ArgumentNullException(nameof(action));
+ _condition = executeCondition;
+ }
+
+ public override void Execute(object parameter) => _action();
+ public override bool CanExecute(object parameter) => _condition?.Invoke() ?? true;
+}
diff --git a/FModel/Framework/NodifyObservableCollection.cs b/FModel/Framework/NodifyObservableCollection.cs
new file mode 100644
index 00000000..ce5eef9a
--- /dev/null
+++ b/FModel/Framework/NodifyObservableCollection.cs
@@ -0,0 +1,192 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.ComponentModel;
+
+namespace FModel.Framework;
+
+public interface INodifyObservableCollection
+{
+ ///
+ /// Called when a new item is added
+ ///
+ /// The callback to execute when an item is added
+ /// Returns self
+ INodifyObservableCollection WhenAdded(Action added);
+
+ ///
+ /// Called when an existing item is removed
+ /// Note: It is not called when items are cleared if is used
+ ///
+ /// The callback to execute when an item is removed
+ /// Returns self
+ INodifyObservableCollection WhenRemoved(Action removed);
+
+ ///
+ /// Called when the collection is cleared
+ /// NOTE: It does not call on each item
+ ///
+ /// The callback to execute when the collection is cleared
+ /// Returns self
+ INodifyObservableCollection WhenCleared(Action> cleared);
+}
+
+public class NodifyObservableCollection : Collection, INodifyObservableCollection, INotifyPropertyChanged, INotifyCollectionChanged
+{
+ protected static readonly PropertyChangedEventArgs IndexerPropertyChanged = new("Item[]");
+ protected static readonly PropertyChangedEventArgs CountPropertyChanged = new("Count");
+ protected static readonly NotifyCollectionChangedEventArgs ResetCollectionChanged = new(NotifyCollectionChangedAction.Reset);
+
+ private readonly List> _added = [];
+ private readonly List> _removed = [];
+ private readonly List>> _cleared = [];
+
+ public event NotifyCollectionChangedEventHandler? CollectionChanged;
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ public NodifyObservableCollection()
+ {
+ }
+
+ public NodifyObservableCollection(IEnumerable collection)
+ : base(new List(collection))
+ {
+ }
+
+ #region Collection Events
+
+ public INodifyObservableCollection WhenAdded(Action added)
+ {
+ if (added != null)
+ {
+ _added.Add(added);
+ }
+ return this;
+ }
+
+ public INodifyObservableCollection WhenRemoved(Action removed)
+ {
+ if (removed != null)
+ {
+ _removed.Add(removed);
+ }
+ return this;
+ }
+
+ public INodifyObservableCollection WhenCleared(Action> cleared)
+ {
+ if (cleared != null)
+ {
+ _cleared.Add(cleared);
+ }
+ return this;
+ }
+
+ protected virtual void NotifyOnItemAdded(T item)
+ {
+ for (int i = 0; i < _added.Count; i++)
+ {
+ _added[i](item);
+ }
+ }
+
+ protected virtual void NotifyOnItemRemoved(T item)
+ {
+ for (int i = 0; i < _removed.Count; i++)
+ {
+ _removed[i](item);
+ }
+ }
+
+ protected virtual void NotifyOnItemsCleared(IList items)
+ {
+ for (int i = 0; i < _cleared.Count; i++)
+ {
+ _cleared[i](items);
+ }
+ }
+
+ #endregion
+
+ #region Collection Handlers
+
+ protected override void ClearItems()
+ {
+ var items = _cleared.Count > 0 || _removed.Count > 0 ? new List(this) : new List();
+ base.ClearItems();
+
+ if (_cleared.Count > 0)
+ {
+ NotifyOnItemsCleared(items);
+ }
+ else if (_removed.Count > 0)
+ {
+ for (int i = 0; i < items.Count; i++)
+ {
+ NotifyOnItemRemoved(items[i]);
+ }
+ }
+
+ OnPropertyChanged(CountPropertyChanged);
+ OnPropertyChanged(IndexerPropertyChanged);
+ OnCollectionChanged(ResetCollectionChanged);
+ }
+
+ protected override void InsertItem(int index, T item)
+ {
+ base.InsertItem(index, item);
+
+ OnPropertyChanged(CountPropertyChanged);
+ OnPropertyChanged(IndexerPropertyChanged);
+ OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index);
+ NotifyOnItemAdded(item);
+ }
+
+ protected override void RemoveItem(int index)
+ {
+ var item = base[index];
+ base.RemoveItem(index);
+
+ OnPropertyChanged(CountPropertyChanged);
+ OnPropertyChanged(IndexerPropertyChanged);
+ OnCollectionChanged(NotifyCollectionChangedAction.Remove, item, index);
+ NotifyOnItemRemoved(item);
+ }
+
+ protected override void SetItem(int index, T item)
+ {
+ T prev = base[index];
+ base.SetItem(index, item);
+ OnPropertyChanged(IndexerPropertyChanged);
+ OnCollectionChanged(NotifyCollectionChangedAction.Replace, prev, item, index);
+ NotifyOnItemRemoved(prev);
+ NotifyOnItemAdded(item);
+ }
+
+ public void Move(int oldIndex, int newIndex)
+ {
+ T prev = base[oldIndex];
+ base.RemoveItem(oldIndex);
+ base.InsertItem(newIndex, prev);
+ OnPropertyChanged(IndexerPropertyChanged);
+ OnCollectionChanged(NotifyCollectionChangedAction.Move, prev, newIndex, oldIndex);
+ }
+
+ protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
+ => CollectionChanged?.Invoke(this, e);
+
+ protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
+ => PropertyChanged?.Invoke(this, args);
+
+ private void OnCollectionChanged(NotifyCollectionChangedAction action, object? item, int index)
+ => OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index));
+
+ private void OnCollectionChanged(NotifyCollectionChangedAction action, object? item, int index, int oldIndex)
+ => OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index, oldIndex));
+
+ private void OnCollectionChanged(NotifyCollectionChangedAction action, object? oldItem, object? newItem, int index)
+ => OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index));
+
+ #endregion
+}
diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs
index 1fe3623a..c1cb5093 100644
--- a/FModel/MainWindow.xaml.cs
+++ b/FModel/MainWindow.xaml.cs
@@ -83,9 +83,9 @@ public partial class MainWindow
).ConfigureAwait(false);
#if DEBUG
- // await _threadWorkerView.Begin(cancellationToken =>
- // _applicationView.CUE4Parse.Extract(cancellationToken,
- // "FortniteGame/Content/Athena/Apollo/Maps/UI/Apollo_Terrain_Minimap.uasset"));
+ await _threadWorkerView.Begin(cancellationToken =>
+ _applicationView.CUE4Parse.Extract(cancellationToken,
+ "FortniteGame/Plugins/GameFeatures/BRCosmetics/Content/Athena/Items/Cosmetics/Characters/Character_Amour.uasset"));
// await _threadWorkerView.Begin(cancellationToken =>
// _applicationView.CUE4Parse.Extract(cancellationToken,
// "FortniteGame/Content/Environments/Helios/Props/GlacierHotel/GlacierHotel_Globe_A/Meshes/SM_GlacierHotel_Globe_A.uasset"));
diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs
index ef4928c9..b13caaf1 100644
--- a/FModel/ViewModels/CUE4ParseViewModel.cs
+++ b/FModel/ViewModels/CUE4ParseViewModel.cs
@@ -8,9 +8,7 @@ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
-
using AdonisUI.Controls;
-
using CUE4Parse.Compression;
using CUE4Parse.Encryption.Aes;
using CUE4Parse.FileProvider;
@@ -53,7 +51,6 @@ using CUE4Parse.UE4.Wwise;
using CUE4Parse_Conversion;
using CUE4Parse_Conversion.Sounds;
-using CUE4Parse.UE4.Assets;
using EpicManifestParser;
using FModel.Creator;
@@ -61,6 +58,7 @@ using FModel.Extensions;
using FModel.Framework;
using FModel.Services;
using FModel.Settings;
+using FModel.ViewModels.Nodify;
using FModel.Views;
using FModel.Views.Resources.Controls;
using FModel.Views.Snooper;
@@ -612,15 +610,18 @@ public class CUE4ParseViewModel : ViewModel
case "uasset":
case "umap":
{
- var exports = Provider.LoadAllObjects(fullPath);
- TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(exports, Formatting.Indented), saveProperties, updateUi);
- if (HasFlag(bulk, EBulkType.Properties)) break; // do not search for viewable exports if we are dealing with jsons
+ var package = Provider.LoadPackage(fullPath);
+ TabControl.SelectedTab.NodifyEditor = new NodifyEditorViewModel(package);
- foreach (var e in exports)
- {
- if (CheckExport(cancellationToken, e, bulk))
- break;
- }
+ // var exports = Provider.LoadAllObjects(fullPath);
+ // TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(exports, Formatting.Indented), saveProperties, updateUi);
+ // if (HasFlag(bulk, EBulkType.Properties)) break; // do not search for viewable exports if we are dealing with jsons
+ //
+ // foreach (var e in exports)
+ // {
+ // if (CheckExport(cancellationToken, e, bulk))
+ // break;
+ // }
break;
}
diff --git a/FModel/ViewModels/Nodify/ConnectionViewModel.cs b/FModel/ViewModels/Nodify/ConnectionViewModel.cs
new file mode 100644
index 00000000..3086e612
--- /dev/null
+++ b/FModel/ViewModels/Nodify/ConnectionViewModel.cs
@@ -0,0 +1,32 @@
+using FModel.Framework;
+
+namespace FModel.ViewModels.Nodify;
+
+public class ConnectionViewModel : ViewModel
+{
+ private ConnectorViewModel _input;
+ public ConnectorViewModel Input
+ {
+ get => _input;
+ set => SetProperty(ref _input, value);
+ }
+
+ private ConnectorViewModel _output;
+ public ConnectorViewModel Output
+ {
+ get => _output;
+ set => SetProperty(ref _output, value);
+ }
+
+ private bool _isSelected;
+ public bool IsSelected
+ {
+ get => _isSelected;
+ set => SetProperty(ref _isSelected, value);
+ }
+
+ public ConnectionViewModel()
+ {
+
+ }
+}
diff --git a/FModel/ViewModels/Nodify/ConnectorViewModel.cs b/FModel/ViewModels/Nodify/ConnectorViewModel.cs
new file mode 100644
index 00000000..9a0d1611
--- /dev/null
+++ b/FModel/ViewModels/Nodify/ConnectorViewModel.cs
@@ -0,0 +1,69 @@
+using System.Windows;
+using CUE4Parse.UE4.Assets.Objects.Properties;
+using FModel.Framework;
+
+namespace FModel.ViewModels.Nodify;
+
+public enum ConnectorFlow
+{
+ Input,
+ Output
+}
+
+public enum ConnectorShape
+{
+ Circle,
+ Triangle,
+ Square,
+}
+
+public class ConnectorViewModel : ViewModel
+{
+ private string? _title;
+ public string? Title
+ {
+ get => _title;
+ set => SetProperty(ref _title, value);
+ }
+
+ private bool _isConnected;
+ public bool IsConnected
+ {
+ get => _isConnected;
+ set => SetProperty(ref _isConnected, value);
+ }
+
+ private Point _anchor;
+ public Point Anchor
+ {
+ get => _anchor;
+ set => SetProperty(ref _anchor, value);
+ }
+
+ private ConnectorShape _shape;
+ public ConnectorShape Shape
+ {
+ get => _shape;
+ set => SetProperty(ref _shape, value);
+ }
+
+ public NodeViewModel Node { get; set; }
+ public ConnectorFlow Flow { get; set; }
+
+ public NodifyObservableCollection Connections { get; } = new();
+
+ public ConnectorViewModel()
+ {
+ Connections.WhenAdded(c => c.Input = this);
+ }
+
+ public ConnectorViewModel(string title) : this()
+ {
+ Title = title;
+ }
+
+ public ConnectorViewModel(FPropertyTagType? tag) : this(tag?.ToString())
+ {
+
+ }
+}
diff --git a/FModel/ViewModels/Nodify/FlowNodeViewModel.cs b/FModel/ViewModels/Nodify/FlowNodeViewModel.cs
new file mode 100644
index 00000000..31e86287
--- /dev/null
+++ b/FModel/ViewModels/Nodify/FlowNodeViewModel.cs
@@ -0,0 +1,60 @@
+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;
+
+namespace FModel.ViewModels.Nodify;
+
+public class FlowNodeViewModel : NodeViewModel
+{
+ private string? _title;
+ public string? Title
+ {
+ get => _title;
+ set => SetProperty(ref _title, value);
+ }
+
+ public NodifyObservableCollection Input { get; } = new();
+ public NodifyObservableCollection Output { get; } = new();
+
+ public FlowNodeViewModel()
+ {
+ Input.WhenAdded(c =>
+ {
+ c.Flow = ConnectorFlow.Input;
+ c.Node = this;
+ });
+ Output.WhenAdded(c =>
+ {
+ c.Flow = ConnectorFlow.Output;
+ c.Node = this;
+ });
+
+ Orientation = Orientation.Horizontal;
+ }
+
+ protected void ConnectOutput(ConnectorViewModel output, FPropertyTagType? tag)
+ {
+ switch (tag)
+ {
+ case FPropertyTagType { Value: not null } s:
+ switch (s.Value.StructType)
+ {
+ case FStructFallback fallback:
+ break;
+ }
+ break;
+ case FPropertyTagType { Value.Properties.Count: > 0 } a:
+ break;
+ case FPropertyTagType { Value: not null } p:
+ break;
+ case FPropertyTagType s:
+ break;
+ case { } t:
+ output.Title = t.GenericValue?.ToString();
+ break;
+ }
+ }
+}
diff --git a/FModel/ViewModels/Nodify/IndexedNodeViewModel.cs b/FModel/ViewModels/Nodify/IndexedNodeViewModel.cs
new file mode 100644
index 00000000..c7ef104a
--- /dev/null
+++ b/FModel/ViewModels/Nodify/IndexedNodeViewModel.cs
@@ -0,0 +1,53 @@
+using System.Collections.Generic;
+using CUE4Parse.UE4.Assets.Objects.Properties;
+using FModel.Framework;
+
+namespace FModel.ViewModels.Nodify;
+
+public class IndexedNodeViewModel : BaseIndexedNodeViewModel
+{
+ public IndexedNodeViewModel(List properties) : base(properties.Count) { }
+
+ protected override void OnArrayIndexChanged()
+ {
+
+ }
+}
+
+public abstract class BaseIndexedNodeViewModel : FlowNodeViewModel
+{
+ private int _arrayIndex = -1;
+ public int ArrayIndex
+ {
+ get => _arrayIndex;
+ set
+ {
+ if (!SetProperty(ref _arrayIndex, value)) return;
+
+ RaisePropertyChanged(nameof(DisplayIndex));
+ NextCommand.RaiseCanExecuteChanged();
+ PreviousCommand.RaiseCanExecuteChanged();
+
+ Input.Clear();
+ Output.Clear();
+ OnArrayIndexChanged();
+ }
+ }
+ public int ArrayMax { get; }
+
+ public DelegateCommand NextCommand { get; }
+ public DelegateCommand PreviousCommand { get; }
+
+ public BaseIndexedNodeViewModel(int arrayMax)
+ {
+ NextCommand = new DelegateCommand(() => ArrayIndex++, () => ArrayIndex < ArrayMax - 1);
+ PreviousCommand = new DelegateCommand(() => ArrayIndex--, () => ArrayIndex > 0);
+
+ ArrayMax = arrayMax;
+ ArrayIndex = 0;
+ }
+
+ protected abstract void OnArrayIndexChanged();
+
+ public int DisplayIndex => ArrayIndex + 1;
+}
diff --git a/FModel/ViewModels/Nodify/NodeViewModel.cs b/FModel/ViewModels/Nodify/NodeViewModel.cs
new file mode 100644
index 00000000..a4704e5b
--- /dev/null
+++ b/FModel/ViewModels/Nodify/NodeViewModel.cs
@@ -0,0 +1,24 @@
+using System.Windows;
+using System.Windows.Controls;
+using FModel.Framework;
+
+namespace FModel.ViewModels.Nodify;
+
+public abstract class NodeViewModel : ViewModel
+{
+ public NodifyEditorViewModel Graph { get; set; }
+
+ private Point _location;
+ public Point Location
+ {
+ get => _location;
+ set => SetProperty(ref _location, value);
+ }
+
+ public Orientation Orientation { get; protected set; }
+
+ protected NodeViewModel()
+ {
+
+ }
+}
diff --git a/FModel/ViewModels/Nodify/NodifyEditorViewModel.cs b/FModel/ViewModels/Nodify/NodifyEditorViewModel.cs
new file mode 100644
index 00000000..78aec7ed
--- /dev/null
+++ b/FModel/ViewModels/Nodify/NodifyEditorViewModel.cs
@@ -0,0 +1,201 @@
+using System.Collections.ObjectModel;
+using System.Windows;
+using CUE4Parse.UE4.Assets;
+using FModel.Framework;
+
+namespace FModel.ViewModels.Nodify;
+
+public class NodifyEditorViewModel : ViewModel
+{
+ public NodifyEditorViewModel()
+ {
+ Nodes
+ .WhenAdded(n => n.Graph = this)
+ .WhenCleared(_ => Connections.Clear());
+ }
+
+ public NodifyEditorViewModel(IPackage package) : this()
+ {
+ var root = new PackagedNodeViewModel(package);
+ Nodes.Add(root);
+ }
+
+ // private void AddToCollection(NodeViewModel parent, NodeViewModel node)
+ // {
+ // parent.Children.Add(Nodes.Count);
+ // Nodes.Add(node);
+ // }
+ //
+ // private void AddPropertyToNode(NodeViewModel parent, IPropertyHolder holder) => AddPropertyToNode(parent, holder.Properties);
+ // private void AddPropertyToNode(NodeViewModel parent, List properties)
+ // {
+ // var node = new NodeViewModel { Title = "Properties", Depth = parent.Depth + 1 };
+ // properties.ForEach(p => AddPropertyToNode(parent, node, p.Name.ToString(), p.Tag, p.TagData));
+ // AddToCollection(parent, node);
+ // }
+ //
+ // private void AddPropertyToNode(NodeViewModel parent, List properties, FPropertyTagData type)
+ // {
+ // var node = new ArrayNodeViewModel { Title = "Properties", Depth = parent.Depth + 1, ArraySize = properties.Count };
+ // AddPropertyToNode(parent, node, "In", properties[0], type);
+ // AddToCollection(parent, node);
+ // }
+ //
+ // private void AddPropertyToNode(NodeViewModel parent, NodeViewModel node, string name, FPropertyTagType tag, FPropertyTagData type)
+ // {
+ // switch (tag)
+ // {
+ // case FPropertyTagType { Value: not null } s:
+ // switch (s.Value.StructType)
+ // {
+ // case FStructFallback fallback:
+ // if (node.Input.Count == 0)
+ // {
+ // // node.Output.RemoveAt(node.Output.Count - 1);
+ // fallback.Properties.ForEach(p => AddPropertyToNode(parent, node, p.Name.ToString(), p.Tag, p.TagData));
+ // }
+ // else
+ // {
+ // AddPropertyToNode(node, fallback);
+ // }
+ // break;
+ // default:
+ // var fields = s.Value.StructType.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public);
+ // AddPropertyToNode(node, fields.Select(field => new FPropertyTag(field, s.Value.StructType)).ToList());
+ // break;
+ // }
+ // break;
+ // case FPropertyTagType { Value.Properties.Count: > 0 } a:
+ // AddPropertyToNode(node, a.Value.Properties, a.Value.InnerTagData);
+ // break;
+ // // case FPropertyTagType { Value: not null } i:
+ // // // special node that if clicked will open the asset
+ // // break;
+ // // case FPropertyTagType s:
+ // // // special node that if clicked will open the asset
+ // // break;
+ // case { } t:
+ // AddPropertyToNode(node, name, t.GenericValue);
+ // break;
+ // default:
+ // AddPropertyToNode(node, name, tag);
+ // break;
+ // }
+ //
+ // if (parent.Output.Count > 0 && node.Input.Count > 0)
+ // {
+ // Connections.Add(new ConnectionViewModel(parent.Output[^1], node.Input[^1]));
+ // }
+ // }
+ //
+ // private void AddPropertyToNode(NodeViewModel node, string name, object? property)
+ // {
+ // node.AddProperty(name, property?.ToString() ?? "null");
+ // }
+ //
+ // private const int MarginX = 64;
+ // private const int MarginY = 16;
+ //
+ // public void OrganizeNodes() => OrganizeNodes(Nodes[^1]);
+ // private void OrganizeNodes(NodeViewModel root)
+ // {
+ // FirstPass(root);
+ // AssignFinalPositions(root);
+ // }
+ //
+ // private double currentLeafY = 0;
+ //
+ // private void FirstPass(NodeViewModel node, double x = 0)
+ // {
+ // node.X = x;
+ //
+ // if (node.Children.Count == 0)
+ // {
+ // node.Y = currentLeafY;
+ // currentLeafY += node.ActualSize.Height + MarginY;
+ // }
+ // else
+ // {
+ // foreach (var child in node.Children)
+ // {
+ // FirstPass(Nodes[child], node.X + node.ActualSize.Width + MarginX);
+ // }
+ //
+ // var leftmost = Nodes[node.Children[0]];
+ // var rightmost = Nodes[node.Children[^1]];
+ // node.Y = (leftmost.Y + rightmost.Y) / 2;
+ // }
+ // }
+ //
+ // private void AssignFinalPositions(NodeViewModel node)
+ // {
+ // node.Location = new Point((int)node.X, (int)node.Y);
+ // foreach (var child in node.Children)
+ // {
+ // AssignFinalPositions(Nodes[child]);
+ // }
+ // }
+
+ private NodifyObservableCollection _nodes = new();
+ public NodifyObservableCollection Nodes
+ {
+ get => _nodes;
+ set => SetProperty(ref _nodes, value);
+ }
+
+ private ObservableCollection _selectedNodes = new();
+ public ObservableCollection SelectedNodes
+ {
+ get => _selectedNodes;
+ set => SetProperty(ref _selectedNodes, value);
+ }
+
+ private NodifyObservableCollection _connections = new();
+ public NodifyObservableCollection Connections
+ {
+ get => _connections;
+ set => SetProperty(ref _connections, value);
+ }
+
+ private ObservableCollection _selectedConnections = new();
+ public ObservableCollection SelectedConnections
+ {
+ get => _selectedConnections;
+ set => SetProperty(ref _selectedConnections, value);
+ }
+
+ private ConnectionViewModel? _selectedConnection;
+ public ConnectionViewModel? SelectedConnection
+ {
+ get => _selectedConnection;
+ set => SetProperty(ref _selectedConnection, value);
+ }
+
+ private NodeViewModel? _selectedNode;
+ public NodeViewModel? SelectedNode
+ {
+ get => _selectedNode;
+ set => SetProperty(ref _selectedNode, value);
+ }
+
+ private Size _viewportSize;
+ public Size ViewportSize
+ {
+ get => _viewportSize;
+ set => SetProperty(ref _viewportSize, value);
+ }
+
+ private Point _viewportLocation;
+ public Point ViewportLocation
+ {
+ get => _viewportLocation;
+ set => SetProperty(ref _viewportLocation, value);
+ }
+
+ private double _viewportZoom;
+ public double ViewportZoom
+ {
+ get => _viewportZoom;
+ set => SetProperty(ref _viewportZoom, value);
+ }
+}
diff --git a/FModel/ViewModels/Nodify/PackagedNodeViewModel.cs b/FModel/ViewModels/Nodify/PackagedNodeViewModel.cs
new file mode 100644
index 00000000..f5243f22
--- /dev/null
+++ b/FModel/ViewModels/Nodify/PackagedNodeViewModel.cs
@@ -0,0 +1,19 @@
+using CUE4Parse.UE4.Assets;
+
+namespace FModel.ViewModels.Nodify;
+
+public class PackagedNodeViewModel(IPackage package) : BaseIndexedNodeViewModel(package.ExportsLazy.Length)
+{
+ protected override void OnArrayIndexChanged()
+ {
+ 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);
+ }
+ }
+}
diff --git a/FModel/ViewModels/TabControlViewModel.cs b/FModel/ViewModels/TabControlViewModel.cs
index a09c1596..2f427de8 100644
--- a/FModel/ViewModels/TabControlViewModel.cs
+++ b/FModel/ViewModels/TabControlViewModel.cs
@@ -16,6 +16,7 @@ using System.Windows;
using System.Windows.Media.Imaging;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse_Conversion.Textures;
+using FModel.ViewModels.Nodify;
namespace FModel.ViewModels;
@@ -108,6 +109,13 @@ public class TabItem : ViewModel
public string FullPath => this.Directory + "/" + this.Header.SubstringBeforeLast(" (");
+ private NodifyEditorViewModel _nodifyEditor;
+ public NodifyEditorViewModel NodifyEditor
+ {
+ get => _nodifyEditor;
+ set => SetProperty(ref _nodifyEditor, value);
+ }
+
private bool _hasSearchOpen;
public bool HasSearchOpen
{
diff --git a/FModel/Views/Resources/Controls/NodeTemplateSelector.cs b/FModel/Views/Resources/Controls/NodeTemplateSelector.cs
new file mode 100644
index 00000000..3eadf974
--- /dev/null
+++ b/FModel/Views/Resources/Controls/NodeTemplateSelector.cs
@@ -0,0 +1,24 @@
+using System.Windows;
+using System.Windows.Controls;
+using FModel.ViewModels.Nodify;
+
+namespace FModel.Views.Resources.Controls;
+
+public class NodeTemplateSelector : DataTemplateSelector
+{
+ public DataTemplate DefaultTemplate { get; set; }
+ public DataTemplate FlowNodeTemplate { get; set; }
+ public DataTemplate IndexedTemplate { get; set; }
+ public DataTemplate PackagedTemplate { get; set; }
+
+ public override DataTemplate SelectTemplate(object item, DependencyObject container)
+ {
+ return item switch
+ {
+ PackagedNodeViewModel => PackagedTemplate,
+ BaseIndexedNodeViewModel => IndexedTemplate,
+ FlowNodeViewModel => FlowNodeTemplate,
+ _ => DefaultTemplate
+ };
+ }
+}
diff --git a/FModel/Views/Resources/Controls/NodifyEditor.xaml b/FModel/Views/Resources/Controls/NodifyEditor.xaml
new file mode 100644
index 00000000..39b545ed
--- /dev/null
+++ b/FModel/Views/Resources/Controls/NodifyEditor.xaml
@@ -0,0 +1,205 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FModel/Views/Resources/Controls/NodifyEditor.xaml.cs b/FModel/Views/Resources/Controls/NodifyEditor.xaml.cs
new file mode 100644
index 00000000..0eaf85ff
--- /dev/null
+++ b/FModel/Views/Resources/Controls/NodifyEditor.xaml.cs
@@ -0,0 +1,12 @@
+using System.Windows.Controls;
+
+namespace FModel.Views.Resources.Controls;
+
+public partial class NodifyEditor : UserControl
+{
+ public NodifyEditor()
+ {
+ InitializeComponent();
+ }
+}
+
diff --git a/FModel/Views/Resources/Resources.xaml b/FModel/Views/Resources/Resources.xaml
index 03e061d9..b0ed74df 100644
--- a/FModel/Views/Resources/Resources.xaml
+++ b/FModel/Views/Resources/Resources.xaml
@@ -675,7 +675,7 @@
-
+