This commit is contained in:
Asval 2024-11-24 06:41:20 +01:00
parent b846878e54
commit eeb11321f7
17 changed files with 932 additions and 16 deletions

View File

@ -156,6 +156,7 @@
<PackageReference Include="ImGui.NET" Version="1.91.0.1" />
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Nodify" Version="6.5.0" />
<PackageReference Include="NVorbis" Version="0.10.5" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
<PackageReference Include="OpenTK" Version="4.8.2" />

View File

@ -15,4 +15,19 @@ public abstract class Command : ICommand
}
public event EventHandler CanExecuteChanged;
}
}
public class DelegateCommand : Command
{
private readonly Action _action;
private readonly Func<bool>? _condition;
public DelegateCommand(Action action, Func<bool>? 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;
}

View File

@ -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<T>
{
/// <summary>
/// Called when a new item is added
/// </summary>
/// <param name="added">The callback to execute when an item is added</param>
/// <returns>Returns self</returns>
INodifyObservableCollection<T> WhenAdded(Action<T> added);
/// <summary>
/// Called when an existing item is removed
/// Note: It is not called when items are cleared if <see cref="WhenCleared(Action{IList{T}})"/> is used
/// </summary>
/// <param name="added">The callback to execute when an item is removed</param>
/// <returns>Returns self</returns>
INodifyObservableCollection<T> WhenRemoved(Action<T> removed);
/// <summary>
/// Called when the collection is cleared
/// NOTE: It does not call <see cref="WhenRemoved(Action{T})"/> on each item
/// </summary>
/// <param name="added">The callback to execute when the collection is cleared</param>
/// <returns>Returns self</returns>
INodifyObservableCollection<T> WhenCleared(Action<IList<T>> cleared);
}
public class NodifyObservableCollection<T> : Collection<T>, INodifyObservableCollection<T>, 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<Action<T>> _added = [];
private readonly List<Action<T>> _removed = [];
private readonly List<Action<IList<T>>> _cleared = [];
public event NotifyCollectionChangedEventHandler? CollectionChanged;
public event PropertyChangedEventHandler? PropertyChanged;
public NodifyObservableCollection()
{
}
public NodifyObservableCollection(IEnumerable<T> collection)
: base(new List<T>(collection))
{
}
#region Collection Events
public INodifyObservableCollection<T> WhenAdded(Action<T> added)
{
if (added != null)
{
_added.Add(added);
}
return this;
}
public INodifyObservableCollection<T> WhenRemoved(Action<T> removed)
{
if (removed != null)
{
_removed.Add(removed);
}
return this;
}
public INodifyObservableCollection<T> WhenCleared(Action<IList<T>> 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<T> 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<T>(this) : new List<T>();
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
}

View File

@ -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"));

View File

@ -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;
}

View File

@ -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()
{
}
}

View File

@ -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<ConnectionViewModel> 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())
{
}
}

View File

@ -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<ConnectorViewModel> Input { get; } = new();
public NodifyObservableCollection<ConnectorViewModel> 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<FScriptStruct> { Value: not null } s:
switch (s.Value.StructType)
{
case FStructFallback fallback:
break;
}
break;
case FPropertyTagType<UScriptArray> { Value.Properties.Count: > 0 } a:
break;
case FPropertyTagType<FPackageIndex> { Value: not null } p:
break;
case FPropertyTagType<FSoftObjectPath> s:
break;
case { } t:
output.Title = t.GenericValue?.ToString();
break;
}
}
}

View File

@ -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<FPropertyTagType> 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;
}

View File

@ -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()
{
}
}

View File

@ -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<FPropertyTag> 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<FPropertyTagType> 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<FScriptStruct> { 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<UScriptArray> { Value.Properties.Count: > 0 } a:
// AddPropertyToNode(node, a.Value.Properties, a.Value.InnerTagData);
// break;
// // case FPropertyTagType<FPackageIndex> { Value: not null } i:
// // // special node that if clicked will open the asset
// // break;
// // case FPropertyTagType<FSoftObjectPath> 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<NodeViewModel> _nodes = new();
public NodifyObservableCollection<NodeViewModel> Nodes
{
get => _nodes;
set => SetProperty(ref _nodes, value);
}
private ObservableCollection<NodeViewModel> _selectedNodes = new();
public ObservableCollection<NodeViewModel> SelectedNodes
{
get => _selectedNodes;
set => SetProperty(ref _selectedNodes, value);
}
private NodifyObservableCollection<ConnectionViewModel> _connections = new();
public NodifyObservableCollection<ConnectionViewModel> Connections
{
get => _connections;
set => SetProperty(ref _connections, value);
}
private ObservableCollection<ConnectionViewModel> _selectedConnections = new();
public ObservableCollection<ConnectionViewModel> 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);
}
}

View File

@ -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);
}
}
}

View File

@ -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
{

View File

@ -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
};
}
}

View File

@ -0,0 +1,205 @@
<UserControl x:Class="FModel.Views.Resources.Controls.NodifyEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:settings="clr-namespace:FModel.Settings"
xmlns:local="clr-namespace:FModel.Views.Resources.Controls"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
xmlns:viewModels="clr-namespace:FModel.ViewModels.Nodify"
xmlns:nodify="https://miroiu.github.io/nodify"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<GeometryDrawing x:Key="SmallGridGeometry"
Geometry="M0,0 L0,1 0.03,1 0.03,0.03 1,0.03 1,0 Z"
Brush="{DynamicResource {x:Static adonisUi:Brushes.Layer4BackgroundBrush}}" />
<GeometryDrawing x:Key="LargeGridGeometry"
Geometry="M0,0 L0,1 0.015,1 0.015,0.015 1,0.015 1,0 Z"
Brush="{DynamicResource {x:Static adonisUi:Brushes.Layer4BackgroundBrush}}" />
<DrawingBrush x:Key="SmallGridLinesDrawingBrush"
TileMode="Tile"
ViewportUnits="Absolute"
Viewport="0 0 20 20"
Transform="{Binding ViewportTransform, ElementName=Editor}"
Drawing="{StaticResource SmallGridGeometry}" />
<DrawingBrush x:Key="LargeGridLinesDrawingBrush"
TileMode="Tile"
ViewportUnits="Absolute"
Opacity="0.5"
Viewport="0 0 100 100"
Transform="{Binding ViewportTransform, ElementName=Editor}"
Drawing="{StaticResource LargeGridGeometry}" />
<SolidColorBrush x:Key="SquareConnectorColor" Color="MediumSlateBlue" />
<SolidColorBrush x:Key="TriangleConnectorColor" Color="MediumVioletRed" />
<SolidColorBrush x:Key="SquareConnectorOutline" Color="MediumSlateBlue" Opacity="0.15" />
<SolidColorBrush x:Key="TriangleConnectorOutline" Color="MediumVioletRed" Opacity="0.15" />
<ControlTemplate x:Key="SquareConnector" TargetType="Control">
<Rectangle Width="14"
Height="14"
StrokeDashCap="Round"
StrokeLineJoin="Round"
StrokeStartLineCap="Round"
StrokeEndLineCap="Round"
Stroke="{TemplateBinding BorderBrush}"
Fill="{TemplateBinding Background}"
StrokeThickness="2" />
</ControlTemplate>
<ControlTemplate x:Key="TriangleConnector" TargetType="Control">
<Polygon Width="14"
Height="14"
Points="1,13 13,13 7,1"
StrokeDashCap="Round"
StrokeLineJoin="Round"
StrokeStartLineCap="Round"
StrokeEndLineCap="Round"
Stroke="{TemplateBinding BorderBrush}"
Fill="{TemplateBinding Background}"
StrokeThickness="2" />
</ControlTemplate>
</UserControl.Resources>
<Grid>
<nodify:NodifyEditor x:Name="Editor"
ItemsSource="{Binding Nodes}"
SelectedItem="{Binding SelectedNode}"
SelectedItems="{Binding SelectedNodes}"
Connections="{Binding Connections}"
SelectedConnection="{Binding SelectedConnection}"
SelectedConnections="{Binding SelectedConnections}"
ViewportLocation="{Binding ViewportLocation, Mode=OneWayToSource}"
ViewportSize="{Binding ViewportSize, Mode=OneWayToSource}"
ViewportZoom="{Binding ViewportZoom, Mode=OneWayToSource}"
Background="{StaticResource SmallGridLinesDrawingBrush}"
ItemTemplateSelector="{DynamicResource NodeTemplateSelector}">
<nodify:NodifyEditor.Resources>
<Style TargetType="{x:Type nodify:Connector}"
BasedOn="{StaticResource {x:Type nodify:Connector}}">
<Setter Property="Anchor" Value="{Binding Anchor, Mode=OneWayToSource}" />
<Setter Property="IsConnected" Value="{Binding IsConnected}" />
</Style>
<Style TargetType="{x:Type nodify:NodeInput}"
BasedOn="{StaticResource {x:Type nodify:NodeInput}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Shape}" Value="{x:Static viewModels:ConnectorShape.Square}">
<Setter Property="ConnectorTemplate" Value="{StaticResource SquareConnector}" />
<Setter Property="BorderBrush" Value="{StaticResource SquareConnectorColor}" />
</DataTrigger>
<DataTrigger Binding="{Binding Shape}" Value="{x:Static viewModels:ConnectorShape.Triangle}">
<Setter Property="ConnectorTemplate" Value="{StaticResource TriangleConnector}" />
<Setter Property="BorderBrush" Value="{StaticResource TriangleConnectorColor}" />
</DataTrigger>
</Style.Triggers>
<Setter Property="Header" Value="{Binding Title}" />
<Setter Property="Anchor" Value="{Binding Anchor, Mode=OneWayToSource}" />
<Setter Property="IsConnected" Value="{Binding IsConnected}" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style TargetType="{x:Type nodify:NodeOutput}"
BasedOn="{StaticResource {x:Type nodify:NodeOutput}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Shape}" Value="{x:Static viewModels:ConnectorShape.Square}">
<Setter Property="ConnectorTemplate" Value="{StaticResource SquareConnector}" />
<Setter Property="BorderBrush" Value="{StaticResource SquareConnectorColor}" />
</DataTrigger>
<DataTrigger Binding="{Binding Shape}" Value="{x:Static viewModels:ConnectorShape.Triangle}">
<Setter Property="ConnectorTemplate" Value="{StaticResource TriangleConnector}" />
<Setter Property="BorderBrush" Value="{StaticResource TriangleConnectorColor}" />
</DataTrigger>
</Style.Triggers>
<Setter Property="Header" Value="{Binding Title}" />
<Setter Property="Anchor" Value="{Binding Anchor, Mode=OneWayToSource}" />
<Setter Property="IsConnected" Value="{Binding IsConnected}" />
<Setter Property="Background" Value="Transparent" />
</Style>
<DataTemplate x:Key="DefaultNodeTemplate" DataType="{x:Type viewModels:NodeViewModel}">
<nodify:Node Header="Dummy" />
</DataTemplate>
<DataTemplate x:Key="FlowNodeTemplate" DataType="{x:Type viewModels:FlowNodeViewModel}">
<nodify:Node Header="{Binding Title}"
Input="{Binding Input}"
Output="{Binding Output}">
</nodify:Node>
</DataTemplate>
<DataTemplate x:Key="IndexedNodeTemplate" DataType="{x:Type viewModels:BaseIndexedNodeViewModel}">
<StackPanel>
<StackPanel Orientation="{Binding Orientation}" Margin="0 0 0 5">
<Button Command="{Binding PreviousCommand}" Padding="2.5">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24">
<Path StrokeThickness="2" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M5 12l14 0" />
<Path StrokeThickness="2" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M5 12l6 6" />
<Path StrokeThickness="2" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M5 12l6 -6" />
</Canvas>
</Viewbox>
</Button>
<Button Command="{Binding NextCommand}" Padding="2.5">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24">
<Path StrokeThickness="2" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M5 12l14 0" />
<Path StrokeThickness="2" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M13 18l6 -6" />
<Path StrokeThickness="2" StrokeLineJoin="Round" StrokeStartLineCap="Round" StrokeEndLineCap="Round" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M13 6l6 6" />
</Canvas>
</Viewbox>
</Button>
</StackPanel>
<nodify:Node Header="{Binding Title}"
Input="{Binding Input}"
Output="{Binding Output}">
<nodify:Node.Footer>
<TextBlock HorizontalAlignment="Right">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}/{1}">
<Binding Path="DisplayIndex" />
<Binding Path="ArrayMax" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</nodify:Node.Footer>
</nodify:Node>
</StackPanel>
</DataTemplate>
<local:NodeTemplateSelector x:Key="NodeTemplateSelector"
DefaultTemplate="{StaticResource DefaultNodeTemplate}"
FlowNodeTemplate="{StaticResource FlowNodeTemplate}"
IndexedTemplate="{StaticResource IndexedNodeTemplate}"
PackagedTemplate="{StaticResource IndexedNodeTemplate}"/>
</nodify:NodifyEditor.Resources>
<nodify:NodifyEditor.ItemContainerStyle>
<Style TargetType="{x:Type nodify:ItemContainer}"
BasedOn="{StaticResource {x:Type nodify:ItemContainer}}">
<Setter Property="Location" Value="{Binding Location}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Panel.ZIndex" Value="1" />
</Trigger>
</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" />
</Grid>
</UserControl>

View File

@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace FModel.Views.Resources.Controls;
public partial class NodifyEditor : UserControl
{
public NodifyEditor()
{
InitializeComponent();
}
}

View File

@ -675,7 +675,7 @@
<ColumnDefinition Width="{Binding AvalonImageSize, Source={x:Static settings:UserSettings.Default}, Mode=TwoWay}" />
</Grid.ColumnDefinitions>
<controls:AvalonEditor x:Name="DynamicArea" Grid.Column="0" DataContext="{Binding SelectedItem, ElementName=TabControlName}" />
<controls:NodifyEditor x:Name="DynamicArea" Grid.Column="0" DataContext="{Binding SelectedItem.NodifyEditor, ElementName=TabControlName}" />
<GridSplitter Grid.Column="1" Width="4" VerticalAlignment="Stretch" ResizeDirection="Columns" KeyboardIncrement="0"
Visibility="{Binding SelectedItem.HasImage, ElementName=TabControlName, Converter={StaticResource BoolToVisibilityConverter}}"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}" />