port from perforce
This commit is contained in:
6
intromat/Intromat/ViewModels/AppStateViewModel.cs
Normal file
6
intromat/Intromat/ViewModels/AppStateViewModel.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public class AppStateViewModel
|
||||
{
|
||||
}
|
||||
}
|
||||
20
intromat/Intromat/ViewModels/CodeGenConnectionViewModel.cs
Normal file
20
intromat/Intromat/ViewModels/CodeGenConnectionViewModel.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Intromat.Views;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public class CodeGenConnectionViewModel : ConnectionViewModel
|
||||
{
|
||||
static CodeGenConnectionViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new CodeGenConnectionView(), typeof(IViewFor<CodeGenConnectionViewModel>));
|
||||
}
|
||||
|
||||
public CodeGenConnectionViewModel(NetworkViewModel parent, NodeInputViewModel input, NodeOutputViewModel output)
|
||||
: base(parent, input, output)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
40
intromat/Intromat/ViewModels/CodeGenInputViewModel.cs
Normal file
40
intromat/Intromat/ViewModels/CodeGenInputViewModel.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using NodeNetwork.ViewModels;
|
||||
using NodeNetwork.Views;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public class CodeGenInputViewModel<T> : ValueNodeInputViewModel<T>
|
||||
{
|
||||
private bool _isMouseOver;
|
||||
|
||||
static CodeGenInputViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new NodeInputView(), typeof(IViewFor<CodeGenInputViewModel<T>>));
|
||||
}
|
||||
|
||||
public CodeGenInputViewModel(EPortType type)
|
||||
{
|
||||
var port = new CodeGenPortViewModel { PortType = type };
|
||||
Port = port;
|
||||
|
||||
if (type == EPortType.Execution)
|
||||
{
|
||||
port.IsPortVisible = true;
|
||||
PortPosition = PortPosition.Right;
|
||||
}
|
||||
else if (type == EPortType.Texture)
|
||||
{
|
||||
port.IsPortVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsMouseOver
|
||||
{
|
||||
get => _isMouseOver;
|
||||
set => this.RaiseAndSetIfChanged(ref _isMouseOver, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
176
intromat/Intromat/ViewModels/CodeGenNetworkViewModel.cs
Normal file
176
intromat/Intromat/ViewModels/CodeGenNetworkViewModel.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using DynamicData;
|
||||
using DynamicData.Alias;
|
||||
using Intromat.Actions.Network;
|
||||
using Intromat.Interfaces;
|
||||
using Intromat.Nodes;
|
||||
using Intromat.ViewModels.Nodes;
|
||||
using Intromat.Views;
|
||||
using NodeNetwork;
|
||||
using NodeNetwork.ViewModels;
|
||||
using NodeNetwork.Views;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public class CodeGenNetworkViewModel : NetworkViewModel, IRefreshProperties
|
||||
{
|
||||
private readonly ObservableAsPropertyHelper<MainEntryPointNode?> _entryPoint;
|
||||
private readonly ObservableAsPropertyHelper<IRefreshProperties?> _selectedObject;
|
||||
private string? _name;
|
||||
private CodeGenNetworkViewModel? _parentNetwork;
|
||||
private int _defaultWidth = 10;
|
||||
private int _defaultHeight = 10;
|
||||
|
||||
static CodeGenNetworkViewModel()
|
||||
{
|
||||
NNViewRegistrar.AddRegistration(() => new CodeGenNetworkView(), typeof(IViewFor<CodeGenNetworkViewModel>));
|
||||
}
|
||||
|
||||
public CodeGenNetworkViewModel(DocumentViewModel document, string name)
|
||||
{
|
||||
_name = name;
|
||||
Document = document;
|
||||
ConnectionFactory = (input, output) => new CodeGenConnectionViewModel(this, input, output);
|
||||
Observable.Select(SelectedNodes.Connect(), _ => SelectedNodes.Count == 1 ? SelectedNodes.Items.First() : (ReactiveObject)this)
|
||||
.StartWith(this)
|
||||
.Cast<IRefreshProperties>()
|
||||
.ToProperty(this, vm => vm.SelectedObject, out _selectedObject);
|
||||
|
||||
var nodes = Nodes.Connect();
|
||||
Observable.Select(nodes, _ => (MainEntryPointNode?)Nodes.Items.FirstOrDefault(vm => vm is MainEntryPointNode))
|
||||
.ToProperty(this, vm => vm.EntryPoint, out _entryPoint);
|
||||
nodes.ActOnEveryObject(OnNodeAdded, OnNodeDeleted);
|
||||
|
||||
var a = Nodes.Connect()
|
||||
.AutoRefreshOnObservable(node => node.Inputs.Connect())
|
||||
.SelectMany(node => node.Inputs.Items)
|
||||
.AutoRefreshOnObservable(input => input.Connections.Connect())
|
||||
.SelectMany(input => input.Connections.Items);
|
||||
|
||||
var b = Nodes.Connect()
|
||||
.AutoRefreshOnObservable(node => node.Outputs.Connect())
|
||||
.SelectMany(node => node.Outputs.Items)
|
||||
.AutoRefreshOnObservable(output => output.Connections.Connect())
|
||||
.SelectMany(output => output.Connections.Items);
|
||||
|
||||
a.Zip(b, (x, _) => x).ActOnEveryObject(OnConnectionAdded, OnConnectionDeleted);
|
||||
|
||||
GroupNodes = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var mainVm = document.MainViewModel;
|
||||
var groupName = await mainVm.SelectName.Handle("Group name:");
|
||||
if (groupName == null)
|
||||
return;
|
||||
|
||||
var groupBinding = (CodeNodeGroupIOBinding?)mainVm.Grouper.MergeIntoGroup(this, SelectedNodes.Items);
|
||||
if (groupBinding == null)
|
||||
return;
|
||||
|
||||
var groupNode = (GroupNodeViewModel)groupBinding.GroupNode;
|
||||
var subNet = (CodeGenNetworkViewModel)groupNode.Subnet!;
|
||||
groupNode.Name = subNet.Name = groupName;
|
||||
groupNode.IOBinding = groupBinding;
|
||||
((GroupSubnetIONodeViewModel)groupBinding.EntranceNode).IOBinding = groupBinding;
|
||||
((GroupSubnetIONodeViewModel)groupBinding.ExitNode).IOBinding = groupBinding;
|
||||
}, this.WhenAnyObservable(vm => vm.SelectedNodes.CountChanged).Select(c => c > 1));
|
||||
|
||||
var isGroupNodeSelected = this.WhenAnyValue(vm => vm.SelectedNodes.CountChanged).Select(_ => SelectedNodes.Count == 1 && SelectedNodes.Items.First() is GroupNodeViewModel);
|
||||
|
||||
UngroupNodes = ReactiveCommand.Create(() =>
|
||||
{
|
||||
var selectedGroupNode = (GroupNodeViewModel)SelectedNodes.Items.First();
|
||||
var mainVm = document.MainViewModel;
|
||||
mainVm.Grouper.Ungroup(selectedGroupNode.IOBinding);
|
||||
}, isGroupNodeSelected);
|
||||
|
||||
OpenGroup = ReactiveCommand.Create(() =>
|
||||
{
|
||||
var selectedGroupNode = (GroupNodeViewModel)SelectedNodes.Items.First();
|
||||
Debug.Assert(selectedGroupNode.Subnet != null, "selectedGroupNode.Subnet != null");
|
||||
|
||||
document.NetworkBreadcrumbBar.ActivePath.Add(new NetworkBreadcrumbViewModel
|
||||
{
|
||||
ViewModel = selectedGroupNode.Subnet,
|
||||
Name = selectedGroupNode.Name
|
||||
});
|
||||
}, isGroupNodeSelected);
|
||||
}
|
||||
|
||||
public IObservableList<PropertyDescriptor>? PropertyDescriptors => null;
|
||||
|
||||
public CodeGenNetworkViewModel? ParentNetwork
|
||||
{
|
||||
get => _parentNetwork;
|
||||
set => this.RaiseAndSetIfChanged(ref _parentNetwork, value);
|
||||
}
|
||||
|
||||
public IRefreshProperties? SelectedObject => _selectedObject.Value;
|
||||
|
||||
public MainEntryPointNode? EntryPoint => _entryPoint.Value;
|
||||
|
||||
public DocumentViewModel Document { get; }
|
||||
|
||||
public string? Name
|
||||
{
|
||||
get => _name;
|
||||
set => this.RaiseAndSetIfChanged(ref _name, value);
|
||||
}
|
||||
|
||||
public int DefaultWidth
|
||||
{
|
||||
get => _defaultWidth;
|
||||
set => this.RaiseAndSetIfChanged(ref _defaultWidth, value);
|
||||
}
|
||||
|
||||
public int DefaultHeight
|
||||
{
|
||||
get => _defaultHeight;
|
||||
set => this.RaiseAndSetIfChanged(ref _defaultHeight, value);
|
||||
}
|
||||
|
||||
protected override void OnDeleteSelectedNodes()
|
||||
{
|
||||
var undoRedo = Document.MainViewModel.UndoRedo;
|
||||
undoRedo.PushGroup(Document);
|
||||
base.OnDeleteSelectedNodes();
|
||||
undoRedo.PopGroup();
|
||||
}
|
||||
|
||||
public override void FinishCut()
|
||||
{
|
||||
var undoRedo = Document.MainViewModel.UndoRedo;
|
||||
undoRedo.PushGroup(Document);
|
||||
base.FinishCut();
|
||||
undoRedo.PopGroup();
|
||||
}
|
||||
|
||||
private void OnNodeDeleted(NodeViewModel node)
|
||||
{
|
||||
Document.MainViewModel.UndoRedo.Record(new DeleteNodeAction(Document, this, node));
|
||||
}
|
||||
|
||||
private void OnNodeAdded(NodeViewModel node)
|
||||
{
|
||||
Document.MainViewModel.UndoRedo.Record(new AddNodeAction(Document, this, node));
|
||||
}
|
||||
|
||||
private void OnConnectionDeleted(ConnectionViewModel connection)
|
||||
{
|
||||
Document.MainViewModel.UndoRedo.Record(new DeleteConnectionAction(Document, this, connection));
|
||||
}
|
||||
|
||||
private void OnConnectionAdded(ConnectionViewModel connection)
|
||||
{
|
||||
Document.MainViewModel.UndoRedo.Record(new AddConnectionAction(Document, this, connection));
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit, Unit> GroupNodes { get; }
|
||||
public ReactiveCommand<Unit, Unit> UngroupNodes { get; }
|
||||
public ReactiveCommand<Unit, Unit> OpenGroup { get; }
|
||||
}
|
||||
}
|
||||
199
intromat/Intromat/ViewModels/CodeGenNodeViewModel.cs
Normal file
199
intromat/Intromat/ViewModels/CodeGenNodeViewModel.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using DynamicData;
|
||||
using DynamicData.Alias;
|
||||
using Intromat.Graphics;
|
||||
using Intromat.Interfaces;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.ViewModels.Previews;
|
||||
using Intromat.Views;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public enum NodeType
|
||||
{
|
||||
Special,
|
||||
Literal,
|
||||
Code,
|
||||
Group
|
||||
}
|
||||
|
||||
public abstract class CodeGenNodeViewModel : NodeViewModel, ICustomTypeDescriptor, IRefreshProperties
|
||||
{
|
||||
private bool _isMouseOver;
|
||||
private NodePreviewViewModelBase? _preview;
|
||||
|
||||
static CodeGenNodeViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<CodeGenNodeViewModel>));
|
||||
}
|
||||
|
||||
protected CodeGenNodeViewModel(NodeType type)
|
||||
{
|
||||
NodeType = type;
|
||||
Guid = Guid.NewGuid();
|
||||
|
||||
var dxHost = Locator.Current.GetService<DxHost>()!;
|
||||
var inputs = Inputs.Connect().Cast(e => (Endpoint)e).ObserveOn(dxHost.MainDispatcher);
|
||||
var outputs = Outputs.Connect().Cast(e => (Endpoint)e).ObserveOn(dxHost.MainDispatcher);
|
||||
|
||||
var leftInputs = inputs.Filter(e => e.PortPosition == PortPosition.Left);
|
||||
var leftOutputs = outputs.Filter(e => e.PortPosition == PortPosition.Left);
|
||||
var rightInputs = inputs.Filter(e => e.PortPosition == PortPosition.Right);
|
||||
var rightOutputs = outputs.Filter(e => e.PortPosition == PortPosition.Right);
|
||||
|
||||
var leftEndpoints = leftInputs.Merge(leftOutputs);
|
||||
var rightEndpoints = rightInputs.Merge(rightOutputs);
|
||||
LeftEndpoints = leftEndpoints
|
||||
.AutoRefreshOnObservable(e => ((CodeGenPortViewModel)e.Port).IsPortVisibleChanged)
|
||||
.Filter(e =>
|
||||
{
|
||||
var port = (CodeGenPortViewModel)e.Port;
|
||||
return port.PortType != EPortType.None && port.IsPortVisible;
|
||||
}).AsObservableList();
|
||||
RightEndpoints = rightEndpoints
|
||||
.AutoRefreshOnObservable(e => ((CodeGenPortViewModel)e.Port).IsPortVisibleChanged)
|
||||
.Filter(e =>
|
||||
{
|
||||
var port = (CodeGenPortViewModel)e.Port;
|
||||
return port.PortType != EPortType.None && port.IsPortVisible;
|
||||
}).AsObservableList();
|
||||
|
||||
PropertyDescriptors = inputs.Merge(outputs)
|
||||
.Where(e => e.Visibility != EndpointVisibility.AlwaysHidden && e.Editor != null)
|
||||
.Transform(CreateEndpointPropertyDescriptor)
|
||||
.AsObservableList();
|
||||
|
||||
this.WhenAnyValue(vm => vm.IsMouseOver).Subscribe(_ =>
|
||||
{
|
||||
foreach (dynamic e in Inputs.Items)
|
||||
{
|
||||
e.IsMouseOver = IsMouseOver;
|
||||
}
|
||||
foreach (dynamic e in Outputs.Items)
|
||||
{
|
||||
e.IsMouseOver = IsMouseOver;
|
||||
}
|
||||
});
|
||||
|
||||
ParentChanged = this.WhenAnyValue(vm => vm.Parent);
|
||||
}
|
||||
|
||||
private PropertyDescriptor CreateEndpointPropertyDescriptor(Endpoint endpoint, int index)
|
||||
{
|
||||
return EndpointPropertyDescriptor.Create(this, endpoint, index);
|
||||
}
|
||||
|
||||
public NodeType NodeType { get; }
|
||||
|
||||
public Guid Guid { get; set; }
|
||||
|
||||
public IObservable<NetworkViewModel> ParentChanged { get; }
|
||||
|
||||
public abstract NodeModelBase CreateModel();
|
||||
|
||||
public virtual void SaveModel(NodeModelBase model)
|
||||
{
|
||||
model.Guid = Guid;
|
||||
model.Name = Name;
|
||||
}
|
||||
|
||||
public virtual void LoadModel(NodeModelBase model)
|
||||
{
|
||||
Guid = model.Guid;
|
||||
Name = model.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The list of properties available for editing
|
||||
/// </summary>
|
||||
public IObservableList<PropertyDescriptor>? PropertyDescriptors { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of visible endpoints that are positioned to the left.
|
||||
/// </summary>
|
||||
public IObservableList<Endpoint> LeftEndpoints { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of visible endpoints that are positioned to the right.
|
||||
/// </summary>
|
||||
public IObservableList<Endpoint> RightEndpoints { get; }
|
||||
|
||||
public bool IsMouseOver
|
||||
{
|
||||
get => _isMouseOver;
|
||||
set => this.RaiseAndSetIfChanged(ref _isMouseOver, value);
|
||||
}
|
||||
|
||||
public NodePreviewViewModelBase? Preview
|
||||
{
|
||||
get => _preview;
|
||||
set => this.RaiseAndSetIfChanged(ref _preview, value);
|
||||
}
|
||||
|
||||
public AttributeCollection GetAttributes()
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
|
||||
public string GetClassName()
|
||||
{
|
||||
return GetType().Name;
|
||||
}
|
||||
|
||||
public string GetComponentName()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public TypeConverter GetConverter()
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
|
||||
public EventDescriptor GetDefaultEvent()
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
|
||||
public PropertyDescriptor GetDefaultProperty()
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
|
||||
public object GetEditor(Type editorBaseType)
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
|
||||
public EventDescriptorCollection GetEvents()
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
|
||||
public EventDescriptorCollection GetEvents(Attribute[]? attributes)
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
|
||||
public PropertyDescriptorCollection GetProperties()
|
||||
{
|
||||
return new(PropertyDescriptors!.Items.ToArray());
|
||||
}
|
||||
|
||||
public PropertyDescriptorCollection GetProperties(Attribute[]? attributes)
|
||||
{
|
||||
return new(PropertyDescriptors!.Items.ToArray());
|
||||
}
|
||||
|
||||
public object GetPropertyOwner(PropertyDescriptor? pd)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
intromat/Intromat/ViewModels/CodeGenOutputViewModel.cs
Normal file
31
intromat/Intromat/ViewModels/CodeGenOutputViewModel.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using NodeNetwork.ViewModels;
|
||||
using NodeNetwork.Views;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public class CodeGenOutputViewModel<T> : ValueNodeOutputViewModel<T>
|
||||
{
|
||||
private bool _isMouseOver;
|
||||
|
||||
static CodeGenOutputViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new NodeOutputView(), typeof(IViewFor<CodeGenOutputViewModel<T>>));
|
||||
}
|
||||
|
||||
public CodeGenOutputViewModel(EPortType type)
|
||||
{
|
||||
Port = new CodeGenPortViewModel { PortType = type, IsPortVisible = true };
|
||||
|
||||
if (type == EPortType.Execution) PortPosition = PortPosition.Left;
|
||||
}
|
||||
|
||||
public bool IsMouseOver
|
||||
{
|
||||
get => _isMouseOver;
|
||||
set => this.RaiseAndSetIfChanged(ref _isMouseOver, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Intromat.Views;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public class CodeGenPendingConnectionViewModel : PendingConnectionViewModel
|
||||
{
|
||||
static CodeGenPendingConnectionViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new CodeGenPendingConnectionView(), typeof(IViewFor<CodeGenPendingConnectionViewModel>));
|
||||
}
|
||||
|
||||
public CodeGenPendingConnectionViewModel(NetworkViewModel parent) : base(parent)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
66
intromat/Intromat/ViewModels/CodeGenPortViewModel.cs
Normal file
66
intromat/Intromat/ViewModels/CodeGenPortViewModel.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using Intromat.Views;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public enum EPortType
|
||||
{
|
||||
None,
|
||||
Execution,
|
||||
Integer,
|
||||
String,
|
||||
Boolean,
|
||||
Float,
|
||||
Texture,
|
||||
Mesh,
|
||||
}
|
||||
|
||||
public class CodeGenPortViewModel : PortViewModel
|
||||
{
|
||||
private EPortType _portType;
|
||||
private bool _isPortVisible;
|
||||
|
||||
static CodeGenPortViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new CodeGenPortView(), typeof(IViewFor<CodeGenPortViewModel>));
|
||||
}
|
||||
|
||||
public CodeGenPortViewModel()
|
||||
{
|
||||
IsPortVisibleChanged = this.WhenAnyValue(vm => vm.IsPortVisible);
|
||||
}
|
||||
|
||||
public EPortType PortType
|
||||
{
|
||||
get => _portType;
|
||||
set => this.RaiseAndSetIfChanged(ref _portType, value);
|
||||
}
|
||||
|
||||
public bool IsPortVisible
|
||||
{
|
||||
get => _isPortVisible;
|
||||
set => this.RaiseAndSetIfChanged(ref _isPortVisible, value);
|
||||
}
|
||||
|
||||
public IObservable<bool> IsPortVisibleChanged { get; }
|
||||
|
||||
public override void OnDragFromPort()
|
||||
{
|
||||
var document = ((CodeGenNetworkViewModel)Parent.Parent.Parent).Document;
|
||||
var undoRedo = document.MainViewModel.UndoRedo;
|
||||
undoRedo.PushGroup(document);
|
||||
base.OnDragFromPort();
|
||||
}
|
||||
|
||||
public override void OnDropOnPort()
|
||||
{
|
||||
var document = ((CodeGenNetworkViewModel)Parent.Parent.Parent).Document;
|
||||
var undoRedo = document.MainViewModel.UndoRedo;
|
||||
base.OnDropOnPort();
|
||||
undoRedo.PopGroup();
|
||||
}
|
||||
}
|
||||
}
|
||||
56
intromat/Intromat/ViewModels/CodeNodeGroupIOBinding.cs
Normal file
56
intromat/Intromat/ViewModels/CodeNodeGroupIOBinding.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using DynamicData;
|
||||
using Intromat.ViewModels.Nodes;
|
||||
using NodeNetwork.Toolkit.Group;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using NodeNetwork.ViewModels;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public class CodeNodeGroupIOBinding : ValueNodeGroupIOBinding
|
||||
{
|
||||
public CodeNodeGroupIOBinding(NodeViewModel groupNode, NodeViewModel entranceNode, NodeViewModel exitNode)
|
||||
: base(groupNode, entranceNode, exitNode)
|
||||
{
|
||||
}
|
||||
|
||||
public override ValueNodeOutputViewModel<T> CreateCompatibleOutput<T>(ValueNodeInputViewModel<T> input)
|
||||
{
|
||||
return (ValueNodeOutputViewModel<T>)CreateOutput(((CodeGenPortViewModel)input.Port).PortType, input.Name, typeof(T), false);
|
||||
}
|
||||
|
||||
public override ValueNodeOutputViewModel<IObservableList<T>> CreateCompatibleOutput<T>(ValueListNodeInputViewModel<T> input)
|
||||
{
|
||||
return (ValueNodeOutputViewModel<IObservableList<T>>)CreateOutput(((CodeGenPortViewModel)input.Port).PortType, input.Name, typeof(T), true);
|
||||
}
|
||||
|
||||
public NodeOutputViewModel CreateOutput(EPortType portType, string name, Type valueType, bool list)
|
||||
{
|
||||
var genericType = list ? typeof(IObservableList<>).MakeGenericType(valueType) : valueType;
|
||||
var outputType = typeof(CodeGenOutputViewModel<>).MakeGenericType(genericType);
|
||||
var editorType = typeof(GroupEndpointEditorViewModel<>).MakeGenericType(genericType);
|
||||
var output = (NodeOutputViewModel)Activator.CreateInstance(outputType, portType)!;
|
||||
var editor = (NodeEndpointEditorViewModel)Activator.CreateInstance(editorType, this)!;
|
||||
output.Name = name;
|
||||
output.Editor = editor;
|
||||
return output;
|
||||
}
|
||||
|
||||
public override ValueNodeInputViewModel<T> CreateCompatibleInput<T>(ValueNodeOutputViewModel<T> output)
|
||||
{
|
||||
return (ValueNodeInputViewModel<T>)CreateInput(((CodeGenPortViewModel)output.Port).PortType, output.Name, typeof(T));
|
||||
}
|
||||
|
||||
public NodeInputViewModel CreateInput(EPortType portType, string name, Type valueType)
|
||||
{
|
||||
var inputType = typeof(CodeGenInputViewModel<>).MakeGenericType(valueType);
|
||||
var editorType = typeof(GroupEndpointEditorViewModel<>).MakeGenericType(valueType);
|
||||
var input = (NodeInputViewModel)Activator.CreateInstance(inputType, portType)!;
|
||||
var editor = (NodeEndpointEditorViewModel)Activator.CreateInstance(editorType, this)!;
|
||||
input.Name = name;
|
||||
input.Editor = editor;
|
||||
input.HideEditorIfConnected = false;
|
||||
return input;
|
||||
}
|
||||
}
|
||||
}
|
||||
64
intromat/Intromat/ViewModels/CodePreviewViewModel.cs
Normal file
64
intromat/Intromat/ViewModels/CodePreviewViewModel.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.Model.Compiler.Error;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public class CodePreviewViewModel : ReactiveObject
|
||||
{
|
||||
private readonly ObservableAsPropertyHelper<string> _compiledCode;
|
||||
|
||||
private IStatement? _code;
|
||||
|
||||
private string? _compilerError;
|
||||
|
||||
public CodePreviewViewModel()
|
||||
{
|
||||
this.WhenAnyValue(vm => vm.Code).Where(c => c != null)
|
||||
.Select(Compile)
|
||||
.ToProperty(this, vm => vm.CompiledCode, out _compiledCode);
|
||||
}
|
||||
|
||||
public IStatement? Code
|
||||
{
|
||||
get => _code;
|
||||
set => this.RaiseAndSetIfChanged(ref _code, value);
|
||||
}
|
||||
|
||||
public string? CompilerError
|
||||
{
|
||||
get => _compilerError;
|
||||
set => this.RaiseAndSetIfChanged(ref _compilerError, value);
|
||||
}
|
||||
|
||||
public string CompiledCode => _compiledCode.Value;
|
||||
|
||||
private string Compile(IStatement? c)
|
||||
{
|
||||
if (c == null)
|
||||
{
|
||||
CompilerError = "No code provided";
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
CompilerError = string.Empty;
|
||||
CompilerContext ctx = new();
|
||||
var sb = new StringBuilder();
|
||||
try
|
||||
{
|
||||
c.CompileHeader(ctx, sb);
|
||||
c.Compile(ctx, sb);
|
||||
return sb.ToString();
|
||||
}
|
||||
catch (CompilerException e)
|
||||
{
|
||||
string trace = string.Join("\n", ctx.VariablesScopesStack.Select(s => s.Identifier));
|
||||
CompilerError = e.Message + "\nProblem is near:\n" + trace;
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
68
intromat/Intromat/ViewModels/DocumentViewModel.cs
Normal file
68
intromat/Intromat/ViewModels/DocumentViewModel.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System.Reactive.Linq;
|
||||
using Intromat.Views;
|
||||
using NodeNetwork.Toolkit.BreadcrumbBar;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public sealed class DocumentViewModel : FileViewModel
|
||||
{
|
||||
private readonly ObservableAsPropertyHelper<ReactiveObject> _currentViewModel;
|
||||
private readonly CodeGenNetworkViewModel _mainNetwork;
|
||||
|
||||
static DocumentViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new DocumentView(), typeof(IViewFor<DocumentViewModel>));
|
||||
}
|
||||
|
||||
public static DocumentViewModel CreateDefault(MainViewModel mainVm, ModuleViewModel module, FolderViewModel parent, string name)
|
||||
{
|
||||
var documentVm = new DocumentViewModel(mainVm, module, parent, name);
|
||||
return documentVm;
|
||||
}
|
||||
|
||||
public DocumentViewModel(MainViewModel mainVm, ModuleViewModel module, FolderViewModel parent, string name)
|
||||
: base(module, parent, name, "igraph")
|
||||
{
|
||||
var network = new CodeGenNetworkViewModel(this, name);
|
||||
_mainNetwork = network;
|
||||
MainViewModel = mainVm;
|
||||
|
||||
this.WhenAnyValue(vm => vm.NetworkBreadcrumbBar.ActiveItem)
|
||||
.Cast<NetworkBreadcrumbViewModel?>()
|
||||
.Where(b => b != null)
|
||||
.Select(b => (CodeGenNetworkViewModel)b!.ViewModel!)
|
||||
.ToProperty(this, vm => vm.CurrentViewModel, out _currentViewModel);
|
||||
|
||||
NetworkBreadcrumbBar.ActivePath.Edit(list =>
|
||||
{
|
||||
list.Clear();
|
||||
list.Add(new NetworkBreadcrumbViewModel
|
||||
{
|
||||
Name = "Main",
|
||||
ViewModel = _mainNetwork
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public override ReactiveObject CurrentViewModel => _currentViewModel.Value;
|
||||
|
||||
public CodeGenNetworkViewModel MainNetwork => _mainNetwork;
|
||||
|
||||
public BreadcrumbBarViewModel NetworkBreadcrumbBar { get; } = new();
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get => base.Name;
|
||||
set
|
||||
{
|
||||
base.Name = value;
|
||||
_mainNetwork.Name = value;
|
||||
// TODO: adjust path!
|
||||
}
|
||||
}
|
||||
|
||||
public MainViewModel MainViewModel { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Intromat.Nodes.Code;
|
||||
using Intromat.Views.Editors;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels.Editors
|
||||
{
|
||||
public class BooleanExpressionEditorViewModel : ExpressionEditorBaseViewModel<bool, BooleanLiteralValue, BooleanLiteralModel>
|
||||
{
|
||||
static BooleanExpressionEditorViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new BooleanExpressionEditorView(), typeof(IViewFor<BooleanExpressionEditorViewModel>));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Intromat.Views.Editors;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels.Editors
|
||||
{
|
||||
public class BooleanValueEditorViewModel : ValueEditorViewModel<bool>
|
||||
{
|
||||
static BooleanValueEditorViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new BooleanValueEditorView(), typeof(IViewFor<BooleanValueEditorViewModel>));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Intromat.Views.Editors;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels.Editors
|
||||
{
|
||||
public class DimensionEditorViewModel : IntegerExpressionEditorViewModel
|
||||
{
|
||||
static DimensionEditorViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new DimensionEditorView(), typeof(IViewFor<DimensionEditorViewModel>));
|
||||
}
|
||||
}
|
||||
}
|
||||
37
intromat/Intromat/ViewModels/Editors/EnumEditorViewModel.cs
Normal file
37
intromat/Intromat/ViewModels/Editors/EnumEditorViewModel.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using Intromat.Interfaces;
|
||||
using Intromat.Nodes.Code;
|
||||
using Intromat.Views.Editors;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels.Editors
|
||||
{
|
||||
public class EnumEditorViewModel : ValueEditorViewModel<int>, IExpressionEditor
|
||||
{
|
||||
static EnumEditorViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new EnumEditorView(), typeof(IViewFor<EnumEditorViewModel>));
|
||||
}
|
||||
|
||||
public EnumEditorViewModel(Type enumType)
|
||||
{
|
||||
EnumType = enumType;
|
||||
}
|
||||
|
||||
public Type EnumType { get; }
|
||||
|
||||
public ERelativeSource RelativeSource
|
||||
{
|
||||
get => ERelativeSource.Custom;
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public bool HasParentSource => false;
|
||||
|
||||
public bool HasInputSource => false;
|
||||
|
||||
public CodeGenPortViewModel CodeGenPort => (CodeGenPortViewModel)Parent.Port;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using Intromat.Interfaces;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.Nodes.Code;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Intromat.ViewModels.Editors
|
||||
{
|
||||
public abstract class ExpressionEditorBaseViewModel<T, TValue, TModel> : ValueEditorViewModel<ITypedExpression<T>?>, IExpressionEditor where TValue : LiteralValueBase<T>, new() where TModel : LiteralModelBase<T>, new()
|
||||
{
|
||||
private T _customValue = default!;
|
||||
private ERelativeSource _relativeSource;
|
||||
private IObservable<T>? _parentValueProvider;
|
||||
private IObservable<T>? _inputValueProvider;
|
||||
private readonly ObservableAsPropertyHelper<T> _parentValue;
|
||||
private readonly ObservableAsPropertyHelper<T> _inputValue;
|
||||
|
||||
protected ExpressionEditorBaseViewModel()
|
||||
{
|
||||
this.WhenAnyObservable(vm => vm.ParentValueProvider)
|
||||
.ToProperty(this, vm => vm.ParentValue, out _parentValue);
|
||||
this.WhenAnyObservable(vm => vm.InputValueProvider)
|
||||
.ToProperty(this, vm => vm.InputValue, out _inputValue);
|
||||
this.WhenAnyValue(vm => vm.ParentValue, vm => vm.InputValue, vm => vm.CustomValue, vm => vm.RelativeSource)
|
||||
.Subscribe(v => Value = CreateLiteralValue(v.Item1, v.Item2, v.Item3, v.Item4));
|
||||
}
|
||||
|
||||
protected LiteralValueBase<T> CreateLiteralValue(T parentValue, T inputValue, T customValue, ERelativeSource relativeSource)
|
||||
{
|
||||
return new TValue
|
||||
{
|
||||
InputValue = inputValue,
|
||||
ParentValue = parentValue,
|
||||
Value = customValue,
|
||||
RelativeSource = relativeSource
|
||||
};
|
||||
}
|
||||
|
||||
public TModel CreateModel()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
RelativeSource = RelativeSource,
|
||||
Value = CustomValue
|
||||
};
|
||||
}
|
||||
|
||||
public void LoadModel(TModel model)
|
||||
{
|
||||
RelativeSource = model.RelativeSource;
|
||||
CustomValue = model.Value;
|
||||
}
|
||||
|
||||
public T CustomValue
|
||||
{
|
||||
get => _customValue;
|
||||
set => this.RaiseAndSetIfChanged(ref _customValue, value);
|
||||
}
|
||||
|
||||
public ERelativeSource RelativeSource
|
||||
{
|
||||
get => _relativeSource;
|
||||
set => this.RaiseAndSetIfChanged(ref _relativeSource, value);
|
||||
}
|
||||
|
||||
public IObservable<T>? ParentValueProvider
|
||||
{
|
||||
get => _parentValueProvider;
|
||||
set => this.RaiseAndSetIfChanged(ref _parentValueProvider, value);
|
||||
}
|
||||
|
||||
public T ParentValue => _parentValue.Value;
|
||||
|
||||
public bool HasParentSource => ParentValueProvider != null;
|
||||
|
||||
public IObservable<T>? InputValueProvider
|
||||
{
|
||||
get => _inputValueProvider;
|
||||
set => this.RaiseAndSetIfChanged(ref _inputValueProvider, value);
|
||||
}
|
||||
|
||||
public T InputValue => _inputValue.Value;
|
||||
|
||||
public bool HasInputSource => InputValueProvider != null;
|
||||
|
||||
public CodeGenPortViewModel CodeGenPort => (CodeGenPortViewModel)Parent.Port;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using Intromat.Nodes.Code;
|
||||
using Intromat.Views.Editors;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels.Editors
|
||||
{
|
||||
public class FloatExpressionEditorViewModel : ExpressionEditorBaseViewModel<float, FloatLiteralValue, FloatLiteralModel>
|
||||
{
|
||||
private float _maxValue;
|
||||
private float _minValue;
|
||||
|
||||
static FloatExpressionEditorViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new FloatExpressionEditorView(), typeof(IViewFor<FloatExpressionEditorViewModel>));
|
||||
}
|
||||
|
||||
public FloatExpressionEditorViewModel()
|
||||
{
|
||||
this.WhenAnyValue(vm => vm.Value)
|
||||
.Subscribe(t =>
|
||||
{
|
||||
var newValue = t!.Evaluate();
|
||||
if (newValue > MaxValue)
|
||||
{
|
||||
var delta = newValue - MinValue;
|
||||
MaxValue = newValue + delta;
|
||||
}
|
||||
else if (newValue < MinValue)
|
||||
{
|
||||
var delta = MaxValue - newValue;
|
||||
MinValue = newValue - delta;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public float MinValue
|
||||
{
|
||||
get => _minValue;
|
||||
set => this.RaiseAndSetIfChanged(ref _minValue, value);
|
||||
}
|
||||
|
||||
public float MaxValue
|
||||
{
|
||||
get => _maxValue;
|
||||
set => this.RaiseAndSetIfChanged(ref _maxValue, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using Intromat.Views.Editors;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels.Editors
|
||||
{
|
||||
public class FloatValueEditorViewModel : ValueEditorViewModel<float>
|
||||
{
|
||||
private float _minValue;
|
||||
private float _maxValue;
|
||||
|
||||
static FloatValueEditorViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new FloatValueEditorView(), typeof(IViewFor<FloatValueEditorViewModel>));
|
||||
}
|
||||
|
||||
public FloatValueEditorViewModel(float min = 0, float max = 1)
|
||||
{
|
||||
MinValue = min;
|
||||
MaxValue = max;
|
||||
Value = 0;
|
||||
|
||||
this.WhenAnyValue(vm => vm.Value)
|
||||
.Subscribe(newValue =>
|
||||
{
|
||||
if (newValue > MaxValue)
|
||||
{
|
||||
var delta = newValue - MinValue;
|
||||
MaxValue = newValue + delta;
|
||||
}
|
||||
else if (newValue < MinValue)
|
||||
{
|
||||
var delta = MaxValue - newValue;
|
||||
MinValue = newValue - delta;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public float MinValue
|
||||
{
|
||||
get => _minValue;
|
||||
set => this.RaiseAndSetIfChanged(ref _minValue, value);
|
||||
}
|
||||
|
||||
public float MaxValue
|
||||
{
|
||||
get => _maxValue;
|
||||
set => this.RaiseAndSetIfChanged(ref _maxValue, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using Intromat.Views.Editors;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// A non-generic interface that provides access to the data in GroupEndpointEditorViewModel.
|
||||
/// Mapping a view onto a generic viewmodel is problematic because the generic type often isn't known in the view,
|
||||
/// and generic views are often not allowed.
|
||||
/// </summary>
|
||||
public interface IGroupEndpointEditorViewModel
|
||||
{
|
||||
public Endpoint Endpoint { get; }
|
||||
public ReactiveCommand<Unit, Unit> MoveUp { get; }
|
||||
public ReactiveCommand<Unit, Unit> MoveDown { get; }
|
||||
public ReactiveCommand<Unit, Unit> Delete { get; }
|
||||
}
|
||||
|
||||
public class GroupEndpointEditorViewModel<T> : ValueEditorViewModel<T>, IGroupEndpointEditorViewModel
|
||||
{
|
||||
static GroupEndpointEditorViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new GroupEndpointEditorView(), typeof(IViewFor<GroupEndpointEditorViewModel<T>>));
|
||||
}
|
||||
|
||||
public GroupEndpointEditorViewModel(CodeNodeGroupIOBinding nodeGroupBinding)
|
||||
{
|
||||
MoveUp = ReactiveCommand.Create(() =>
|
||||
{
|
||||
var isInput = Parent is NodeInputViewModel;
|
||||
IEnumerable<Endpoint> endpoints = isInput ? Parent.Parent.Inputs.Items : Parent.Parent.Outputs.Items;
|
||||
|
||||
// Swap SortIndex of this endpoint with the SortIndex of the previous endpoint in the list, if any.
|
||||
var prevElement = endpoints
|
||||
.Where(e => e.SortIndex < Parent.SortIndex)
|
||||
.MaxBy(e => e.SortIndex);
|
||||
if (prevElement != null)
|
||||
{
|
||||
var idx = prevElement.SortIndex;
|
||||
prevElement.SortIndex = Parent.SortIndex;
|
||||
Parent.SortIndex = idx;
|
||||
}
|
||||
});
|
||||
MoveDown = ReactiveCommand.Create(() =>
|
||||
{
|
||||
var isInput = Parent is NodeInputViewModel;
|
||||
IEnumerable<Endpoint> endpoints = isInput ? Parent.Parent.Inputs.Items : Parent.Parent.Outputs.Items;
|
||||
|
||||
var nextElement = endpoints
|
||||
.Where(e => e.SortIndex > Parent.SortIndex)
|
||||
.MinBy(e => e.SortIndex);
|
||||
if (nextElement != null)
|
||||
{
|
||||
var idx = nextElement.SortIndex;
|
||||
nextElement.SortIndex = Parent.SortIndex;
|
||||
Parent.SortIndex = idx;
|
||||
}
|
||||
});
|
||||
Delete = ReactiveCommand.Create(() => { nodeGroupBinding.DeleteEndpoint(Parent); });
|
||||
}
|
||||
|
||||
public Endpoint Endpoint => Parent;
|
||||
public ReactiveCommand<Unit, Unit> MoveUp { get; }
|
||||
public ReactiveCommand<Unit, Unit> MoveDown { get; }
|
||||
public ReactiveCommand<Unit, Unit> Delete { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using Intromat.Nodes.Code;
|
||||
using Intromat.Views.Editors;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels.Editors
|
||||
{
|
||||
public class IntegerExpressionEditorViewModel : ExpressionEditorBaseViewModel<int, IntLiteralValue, IntLiteralModel>
|
||||
{
|
||||
private int _maxValue;
|
||||
private int _minValue;
|
||||
|
||||
static IntegerExpressionEditorViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new IntegerExpressionEditorView(), typeof(IViewFor<IntegerExpressionEditorViewModel>));
|
||||
}
|
||||
|
||||
public IntegerExpressionEditorViewModel()
|
||||
{
|
||||
this.WhenAnyValue(vm => vm.CustomValue)
|
||||
.Subscribe(newValue =>
|
||||
{
|
||||
if (newValue > MaxValue)
|
||||
{
|
||||
var delta = newValue - MinValue;
|
||||
MaxValue = newValue + delta;
|
||||
}
|
||||
else if (newValue < MinValue)
|
||||
{
|
||||
var delta = MaxValue - newValue;
|
||||
MinValue = newValue - delta;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public int MinValue
|
||||
{
|
||||
get => _minValue;
|
||||
set => this.RaiseAndSetIfChanged(ref _minValue, value);
|
||||
}
|
||||
|
||||
public int MaxValue
|
||||
{
|
||||
get => _maxValue;
|
||||
set => this.RaiseAndSetIfChanged(ref _maxValue, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using Intromat.Views.Editors;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels.Editors
|
||||
{
|
||||
public class IntegerValueEditorViewModel : ValueEditorViewModel<int>
|
||||
{
|
||||
private int _minValue;
|
||||
private int _maxValue;
|
||||
|
||||
static IntegerValueEditorViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new IntegerValueEditorView(), typeof(IViewFor<IntegerValueEditorViewModel>));
|
||||
}
|
||||
|
||||
public IntegerValueEditorViewModel(int min = 0, int max = 100)
|
||||
{
|
||||
MinValue = min;
|
||||
MaxValue = max;
|
||||
Value = 0;
|
||||
|
||||
this.WhenAnyValue(vm => vm.Value)
|
||||
.Subscribe(newValue =>
|
||||
{
|
||||
if (newValue > MaxValue)
|
||||
{
|
||||
var delta = newValue - MinValue;
|
||||
MaxValue = newValue + delta;
|
||||
}
|
||||
else if (newValue < MinValue)
|
||||
{
|
||||
var delta = MaxValue - newValue;
|
||||
MinValue = newValue - delta;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public int MinValue
|
||||
{
|
||||
get => _minValue;
|
||||
set => this.RaiseAndSetIfChanged(ref _minValue, value);
|
||||
}
|
||||
|
||||
public int MaxValue
|
||||
{
|
||||
get => _maxValue;
|
||||
set => this.RaiseAndSetIfChanged(ref _maxValue, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.Nodes.Textures;
|
||||
using Intromat.Views.Editors;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using ReactiveUI;
|
||||
using SharpDX.Direct3D11;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels.Editors
|
||||
{
|
||||
public class SamplerEditorViewModel : ExpressionEditorBaseViewModel<SamplerDesc, SamplerLiteralValue, SamplerLiteralModel>
|
||||
{
|
||||
private Filter _filter = SamplerStateDescription.Default().Filter;
|
||||
private TextureAddressMode _address = SamplerStateDescription.Default().AddressU;
|
||||
|
||||
static SamplerEditorViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new SamplerEditorView(), typeof(IViewFor<SamplerEditorViewModel>));
|
||||
}
|
||||
|
||||
public SamplerEditorViewModel()
|
||||
{
|
||||
this.WhenAnyValue(vm => vm.CustomValue)
|
||||
.Where(s => s != null)
|
||||
.Subscribe(s =>
|
||||
{
|
||||
Filter = s.Filter;
|
||||
Address = s.Address;
|
||||
});
|
||||
|
||||
this.WhenAnyValue(vm => vm.Filter, vm => vm.Address)
|
||||
.Subscribe((p) =>
|
||||
{
|
||||
CustomValue = new SamplerDesc()
|
||||
{
|
||||
Filter = p.Item1,
|
||||
Address = p.Item2
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public Filter Filter
|
||||
{
|
||||
get => _filter;
|
||||
set => this.RaiseAndSetIfChanged(ref _filter, value);
|
||||
}
|
||||
|
||||
public TextureAddressMode Address
|
||||
{
|
||||
get => _address;
|
||||
set => this.RaiseAndSetIfChanged(ref _address, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Intromat.Nodes.Code;
|
||||
using Intromat.Views.Editors;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels.Editors
|
||||
{
|
||||
public class StringExpressionEditorViewModel : ExpressionEditorBaseViewModel<string?, StringLiteralValue, StringLiteralModel>
|
||||
{
|
||||
static StringExpressionEditorViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new StringExpressionEditorView(), typeof(IViewFor<StringExpressionEditorViewModel>));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Intromat.Views.Editors;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels.Editors
|
||||
{
|
||||
public class StringValueEditorViewModel : ValueEditorViewModel<string?>
|
||||
{
|
||||
static StringValueEditorViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new StringValueEditorView(), typeof(IViewFor<StringValueEditorViewModel>));
|
||||
}
|
||||
|
||||
public StringValueEditorViewModel()
|
||||
{
|
||||
Value = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
103
intromat/Intromat/ViewModels/EndpointPropertyDescriptor.cs
Normal file
103
intromat/Intromat/ViewModels/EndpointPropertyDescriptor.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
using Xceed.Wpf.Toolkit.PropertyGrid;
|
||||
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||
using Xceed.Wpf.Toolkit.PropertyGrid.Editors;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public class EndpointEditor : ITypeEditor
|
||||
{
|
||||
public FrameworkElement ResolveEditor(PropertyItem propertyItem)
|
||||
{
|
||||
var editor = ((EndpointPropertyDescriptor)propertyItem.PropertyDescriptor).Endpoint.Editor;
|
||||
return new ViewModelViewHost { ViewModel = editor, HorizontalContentAlignment = HorizontalAlignment.Stretch, VerticalContentAlignment = VerticalAlignment.Stretch };
|
||||
}
|
||||
}
|
||||
|
||||
public class EndpointPropertyDescriptor : PropertyDescriptor
|
||||
{
|
||||
public EndpointPropertyDescriptor(CodeGenNodeViewModel node, Endpoint endpoint, Attribute[] attrs)
|
||||
: base(endpoint.Name, attrs)
|
||||
{
|
||||
Node = node;
|
||||
Endpoint = endpoint;
|
||||
ComponentType = typeof(CodeGenNodeViewModel);
|
||||
PropertyType = endpoint.Editor.GetType();
|
||||
IsReadOnly = false;
|
||||
}
|
||||
|
||||
public static EndpointPropertyDescriptor Create(CodeGenNodeViewModel node, Endpoint endpoint, int index)
|
||||
{
|
||||
var attributes = new List<Attribute>();
|
||||
if (endpoint.Group != null)
|
||||
{
|
||||
attributes.Add(new CategoryAttribute(endpoint.Group.Name));
|
||||
}
|
||||
else
|
||||
{
|
||||
attributes.Add(new CategoryAttribute(node.Name));
|
||||
}
|
||||
|
||||
attributes.Add(new PropertyOrderAttribute(index));
|
||||
attributes.Add(new EditorAttribute(typeof(EndpointEditor), typeof(EndpointEditor)));
|
||||
return new EndpointPropertyDescriptor(node, endpoint, attributes.ToArray());
|
||||
}
|
||||
|
||||
public override bool CanResetValue(object component)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override object GetValue(object? component)
|
||||
{
|
||||
return GetEditorValue((dynamic)Endpoint.Editor);
|
||||
}
|
||||
|
||||
public override void ResetValue(object component)
|
||||
{
|
||||
ResetEditorValue((dynamic)Endpoint.Editor);
|
||||
}
|
||||
|
||||
public override void SetValue(object? component, object? value)
|
||||
{
|
||||
SetEditorValue((dynamic)Endpoint.Editor, (dynamic?)value);
|
||||
}
|
||||
|
||||
private void SetEditorValue<T>(ValueEditorViewModel<T> editor, T value)
|
||||
{
|
||||
editor.Value = value;
|
||||
Node.RaisePropertyChanged(Name);
|
||||
}
|
||||
|
||||
private void ResetEditorValue<T>(ValueEditorViewModel<T> editor)
|
||||
{
|
||||
SetEditorValue(editor!, default!);
|
||||
}
|
||||
|
||||
private static T GetEditorValue<T>(ValueEditorViewModel<T> editor)
|
||||
{
|
||||
return editor.Value;
|
||||
}
|
||||
|
||||
public override bool ShouldSerializeValue(object component)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override Type ComponentType { get; }
|
||||
|
||||
public override bool IsReadOnly { get; }
|
||||
|
||||
public override Type PropertyType { get; }
|
||||
|
||||
public CodeGenNodeViewModel Node { get; }
|
||||
|
||||
public Endpoint Endpoint { get; }
|
||||
}
|
||||
}
|
||||
156
intromat/Intromat/ViewModels/ExplorerViewModel.cs
Normal file
156
intromat/Intromat/ViewModels/ExplorerViewModel.cs
Normal file
@@ -0,0 +1,156 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Intromat.Actions.Hierarchy;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public class ExplorerViewModel : ReactiveObject
|
||||
{
|
||||
private ModuleViewModel? _currentModule;
|
||||
private ProjectViewModel? _currentProject;
|
||||
private FolderViewModel? _currentFolder;
|
||||
private FileViewModel? _currentFile;
|
||||
private readonly MainViewModel _mainVm;
|
||||
|
||||
public ExplorerViewModel(MainViewModel mainVm)
|
||||
{
|
||||
_mainVm = mainVm;
|
||||
var isCurrentProjectSet = this.WhenAnyValue(vm => vm.CurrentProject).Select(p => p != null);
|
||||
var isCurrentModuleSet = this.WhenAnyValue(vm => vm.CurrentModule).Select(p => p != null);
|
||||
var isCurrentFolderSet = this.WhenAnyValue(vm => vm.CurrentFolder).Select(p => p != null);
|
||||
var isCurrentDocumentSet = this.WhenAnyValue(vm => vm.CurrentFile).Select(p => p != null);
|
||||
|
||||
NewModule = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var moduleName = await mainVm.SelectName.Handle("Enter the new module's name:");
|
||||
if (moduleName != null)
|
||||
{
|
||||
mainVm.UndoRedo.Execute(new CreateModuleAction(moduleName, CurrentProject!));
|
||||
}
|
||||
}, isCurrentProjectSet);
|
||||
|
||||
RenameModule = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var newName = await mainVm.SelectName.Handle("Enter the module's new name:");
|
||||
if (newName != null)
|
||||
{
|
||||
var oldName = CurrentModule!.Name;
|
||||
mainVm.UndoRedo.Execute(new UndoItem<ModuleViewModel, string>(CurrentModule, CurrentModule, vm => vm.Name, oldName, newName));
|
||||
}
|
||||
}, isCurrentModuleSet);
|
||||
|
||||
DeleteModule = ReactiveCommand.CreateFromTask(() =>
|
||||
{
|
||||
mainVm.UndoRedo.Execute(new DeleteModuleAction(CurrentModule!));
|
||||
return Task.CompletedTask;
|
||||
}, isCurrentModuleSet);
|
||||
|
||||
NewModuleFolder = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var moduleName = await mainVm.SelectName.Handle("Enter the new folder's name:");
|
||||
if (moduleName != null)
|
||||
{
|
||||
mainVm.UndoRedo.Execute(new CreateFolderAction(moduleName, CurrentModule!, CurrentModule!));
|
||||
}
|
||||
}, isCurrentModuleSet);
|
||||
|
||||
NewSubFolder = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var moduleName = await mainVm.SelectName.Handle("Enter the new folder's name:");
|
||||
if (moduleName != null)
|
||||
{
|
||||
mainVm.UndoRedo.Execute(new CreateFolderAction(moduleName, CurrentFolder!.Module, CurrentFolder));
|
||||
}
|
||||
}, isCurrentFolderSet);
|
||||
|
||||
RenameFolder = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var newName = await mainVm.SelectName.Handle("Enter the folder's new name:");
|
||||
if (newName != null)
|
||||
{
|
||||
var oldName = CurrentFolder!.Name;
|
||||
mainVm.UndoRedo.Execute(new UndoItem<FolderViewModel, string>(CurrentFolder.Module, CurrentFolder, vm => vm.Name, oldName, newName));
|
||||
}
|
||||
}, isCurrentFolderSet);
|
||||
|
||||
DeleteFolder = ReactiveCommand.CreateFromTask(() =>
|
||||
{
|
||||
mainVm.UndoRedo.Execute(new DeleteFolderAction(CurrentFolder!));
|
||||
return Task.CompletedTask;
|
||||
}, isCurrentFolderSet);
|
||||
|
||||
NewFile = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var fileAction = await mainVm.SelectTypeAndFileName.Handle(Unit.Default);
|
||||
if (fileAction != null)
|
||||
{
|
||||
mainVm.UndoRedo.Execute(new CreateFileAction(mainVm, fileAction(), CurrentFolder!.Module, CurrentFolder));
|
||||
}
|
||||
}, isCurrentFolderSet);
|
||||
|
||||
RenameFile = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var newName = await mainVm.SelectName.Handle("Enter the graph's new name:");
|
||||
if (newName != null)
|
||||
{
|
||||
var oldName = CurrentFile!.Name;
|
||||
mainVm.UndoRedo.Execute(new UndoItem<FileViewModel, string>(CurrentFile.Module, CurrentFile, vm => vm.Name, oldName, newName));
|
||||
}
|
||||
}, isCurrentDocumentSet);
|
||||
|
||||
DeleteFile = ReactiveCommand.CreateFromTask(() =>
|
||||
{
|
||||
mainVm.UndoRedo.Execute(new DeleteFileAction(CurrentFile!));
|
||||
return Task.CompletedTask;
|
||||
}, isCurrentDocumentSet);
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit, Unit> NewModule { get; set; }
|
||||
public ReactiveCommand<Unit, Unit> RenameModule { get; set; }
|
||||
public ReactiveCommand<Unit, Unit> DeleteModule { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> NewModuleFolder { get; set; }
|
||||
public ReactiveCommand<Unit, Unit> NewSubFolder { get; set; }
|
||||
public ReactiveCommand<Unit, Unit> RenameFolder { get; set; }
|
||||
public ReactiveCommand<Unit, Unit> DeleteFolder { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> NewFile { get; set; }
|
||||
public ReactiveCommand<Unit, Unit> RenameFile { get; set; }
|
||||
public ReactiveCommand<Unit, Unit> DeleteFile { get; set; }
|
||||
|
||||
public ObservableCollection<ProjectViewModel> TreeRoot { get; } = new();
|
||||
|
||||
public ProjectViewModel? CurrentProject
|
||||
{
|
||||
get => _currentProject;
|
||||
set => this.RaiseAndSetIfChanged(ref _currentProject, value);
|
||||
}
|
||||
|
||||
public ModuleViewModel? CurrentModule
|
||||
{
|
||||
get => _currentModule;
|
||||
set
|
||||
{
|
||||
this.RaiseAndSetIfChanged(ref _currentModule, value);
|
||||
CurrentFolder = _currentModule;
|
||||
}
|
||||
}
|
||||
|
||||
public FolderViewModel? CurrentFolder
|
||||
{
|
||||
get => _currentFolder;
|
||||
set => this.RaiseAndSetIfChanged(ref _currentFolder, value);
|
||||
}
|
||||
|
||||
public FileViewModel? CurrentFile
|
||||
{
|
||||
get => _currentFile;
|
||||
set => this.RaiseAndSetIfChanged(ref _currentFile, value);
|
||||
}
|
||||
|
||||
public MainViewModel MainViewModel => _mainVm;
|
||||
}
|
||||
}
|
||||
57
intromat/Intromat/ViewModels/FileViewModel.cs
Normal file
57
intromat/Intromat/ViewModels/FileViewModel.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System.IO;
|
||||
using Intromat.Interfaces;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public abstract class FileViewModel : ReactiveObject, ITreeItem, IFile
|
||||
{
|
||||
private bool _isExpanded;
|
||||
private bool _isSelected;
|
||||
private readonly FolderViewModel _parent;
|
||||
private readonly ModuleViewModel _module;
|
||||
private string _name;
|
||||
private bool _isDirty;
|
||||
private readonly string _extension;
|
||||
|
||||
public FileViewModel(ModuleViewModel module, FolderViewModel parent, string name, string extension)
|
||||
{
|
||||
_extension = extension;
|
||||
_module = module;
|
||||
_parent = parent;
|
||||
_name = name;
|
||||
}
|
||||
|
||||
public abstract ReactiveObject CurrentViewModel { get; }
|
||||
|
||||
public string FullPath => Path.Combine(_parent.FullPath, $"{_name}.{_extension}");
|
||||
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => _isExpanded;
|
||||
set => this.RaiseAndSetIfChanged(ref _isExpanded, value);
|
||||
}
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set => this.RaiseAndSetIfChanged(ref _isSelected, value);
|
||||
}
|
||||
|
||||
public virtual string Name
|
||||
{
|
||||
get => _name;
|
||||
set => this.RaiseAndSetIfChanged(ref _name, value);
|
||||
}
|
||||
|
||||
public FolderViewModel Parent => _parent;
|
||||
|
||||
public ModuleViewModel Module => _module;
|
||||
|
||||
public bool IsDirty
|
||||
{
|
||||
get => _isDirty;
|
||||
set => this.RaiseAndSetIfChanged(ref _isDirty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
intromat/Intromat/ViewModels/FolderViewModel.cs
Normal file
83
intromat/Intromat/ViewModels/FolderViewModel.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Reactive.Linq;
|
||||
using DynamicData;
|
||||
using DynamicData.Binding;
|
||||
using Intromat.Interfaces;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public class FolderViewModel : ReactiveObject, ITreeItem
|
||||
{
|
||||
protected string _name;
|
||||
protected bool _isExpanded;
|
||||
protected bool _isSelected;
|
||||
protected ITreeItem _parent;
|
||||
protected readonly ObservableCollectionExtended<ITreeItem> _treeChildren = new();
|
||||
protected readonly ModuleViewModel _module;
|
||||
|
||||
public FolderViewModel(ModuleViewModel? module, ITreeItem parent, string name)
|
||||
{
|
||||
_module = module ?? (ModuleViewModel)this;
|
||||
_parent = parent;
|
||||
_name = name;
|
||||
var folders = Folders.Connect();
|
||||
var files = Files.Connect();
|
||||
files
|
||||
.Cast(d => (ITreeItem)d)
|
||||
.Merge(folders.Cast(f => (ITreeItem)f))
|
||||
.Sort(TreeChildrenComparer)
|
||||
.Bind(_treeChildren)
|
||||
.DisposeMany()
|
||||
.Subscribe();
|
||||
}
|
||||
|
||||
public IComparer<ITreeItem> TreeChildrenComparer { get; } = Comparer<ITreeItem>.Create((i1, i2) =>
|
||||
{
|
||||
if (i1 is FolderViewModel && i2 is not FolderViewModel)
|
||||
return -1;
|
||||
if (i1 is not FolderViewModel && i2 is FolderViewModel)
|
||||
return 1;
|
||||
|
||||
return String.Compare(i1.Name, i2.Name, StringComparison.Ordinal);
|
||||
});
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set
|
||||
{
|
||||
this.RaiseAndSetIfChanged(ref _name, value);
|
||||
// TODO: adjust path!
|
||||
}
|
||||
}
|
||||
|
||||
public virtual string FullPath => Path.Combine(((FolderViewModel)_parent).FullPath, _name);
|
||||
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => _isExpanded;
|
||||
set => this.RaiseAndSetIfChanged(ref _isExpanded, value);
|
||||
}
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set => this.RaiseAndSetIfChanged(ref _isSelected, value);
|
||||
}
|
||||
|
||||
public ITreeItem Parent
|
||||
{
|
||||
get => _parent;
|
||||
set => this.RaiseAndSetIfChanged(ref _parent, value);
|
||||
}
|
||||
|
||||
public ModuleViewModel Module => _module;
|
||||
public ISourceList<FolderViewModel> Folders { get; } = new SourceList<FolderViewModel>();
|
||||
public ISourceList<FileViewModel> Files { get; } = new SourceList<FileViewModel>();
|
||||
public ObservableCollection<ITreeItem> TreeChildren => _treeChildren;
|
||||
}
|
||||
}
|
||||
171
intromat/Intromat/ViewModels/MainViewModel.cs
Normal file
171
intromat/Intromat/ViewModels/MainViewModel.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.ViewModels.Nodes;
|
||||
using Intromat.ViewModels.Previews;
|
||||
using NodeNetwork.Toolkit.Group;
|
||||
using NodeNetwork.Toolkit.NodeList;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public class MainViewModel : ReactiveObject
|
||||
{
|
||||
private ProjectViewModel _project;
|
||||
private FileViewModel? _activeFile;
|
||||
private object? _propertiesContext;
|
||||
private DxTexturePreviewViewModel? _preview2dContext;
|
||||
private DxMeshPreviewViewModel? _preview3dContext;
|
||||
|
||||
public MainViewModel()
|
||||
{
|
||||
AppState = ((AppStateViewModel)RxApp.SuspensionHost.AppState!)!;
|
||||
Explorer = new(this);
|
||||
|
||||
var desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
||||
var initialProjectPath = Path.Combine(desktopPath, "Project1", "Project1.iproj");
|
||||
if (File.Exists(initialProjectPath))
|
||||
{
|
||||
_project = Task.Run(() => ProjectSerializer.LoadProject(this, initialProjectPath)).GetAwaiter().GetResult()!;
|
||||
}
|
||||
else
|
||||
{
|
||||
_project = ProjectViewModel.CreateDefault(this, initialProjectPath, "Project1");
|
||||
}
|
||||
|
||||
foreach (var nodeType in typeof(CodeGenNodeViewModel).Assembly.ExportedTypes.Where(t => typeof(CodeGenNodeViewModel).IsAssignableFrom(t) && !t.IsAbstract))
|
||||
{
|
||||
if (nodeType.Namespace == $"{nameof(Intromat)}.{nameof(Nodes)}")
|
||||
continue;
|
||||
|
||||
NodeList.AddNodeType(() => (CodeGenNodeViewModel)Activator.CreateInstance(nodeType)!);
|
||||
}
|
||||
|
||||
Grouper = new NodeGrouper
|
||||
{
|
||||
GroupNodeFactory = subnet => new GroupNodeViewModel
|
||||
{
|
||||
Name = "Group",
|
||||
Subnet = (CodeGenNetworkViewModel)subnet
|
||||
},
|
||||
EntranceNodeFactory = () => new GroupSubnetIONodeViewModel
|
||||
{
|
||||
IsEntranceNode = true
|
||||
},
|
||||
ExitNodeFactory = () => new GroupSubnetIONodeViewModel
|
||||
{
|
||||
IsEntranceNode = false
|
||||
},
|
||||
SubNetworkFactory = parentNet =>
|
||||
{
|
||||
var subNetwork = new CodeGenNetworkViewModel(((CodeGenNetworkViewModel)parentNet).Document, "Group") { ParentNetwork = (CodeGenNetworkViewModel)parentNet };
|
||||
return subNetwork;
|
||||
},
|
||||
IOBindingFactory = (groupNode, entranceNode, exitNode) =>
|
||||
new CodeNodeGroupIOBinding(groupNode, entranceNode, exitNode)
|
||||
};
|
||||
|
||||
LoadProject = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var file = await OpenProjectFile.Handle(true);
|
||||
if (file == null)
|
||||
return null;
|
||||
|
||||
var activeFile = ActiveFile;
|
||||
if (activeFile != null)
|
||||
UndoRedo.Purge(activeFile);
|
||||
|
||||
UndoRedo.Recording = false;
|
||||
try
|
||||
{
|
||||
return await ProjectSerializer.LoadProject(this, file);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored, TODO
|
||||
}
|
||||
finally
|
||||
{
|
||||
ActiveFile = null;
|
||||
Files.Clear();
|
||||
UndoRedo.Recording = true;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
LoadProject.Where(n => n != null).Subscribe(project => Project = project!);
|
||||
|
||||
SaveProject = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await ProjectSerializer.SaveModel(Project);
|
||||
});
|
||||
|
||||
SaveProjectAs = ReactiveCommand.CreateFromTask(() => throw new NotImplementedException());
|
||||
|
||||
this.WhenAnyValue(vm => vm.Project).Subscribe(_ =>
|
||||
{
|
||||
if (Explorer.TreeRoot.Count == 0)
|
||||
Explorer.TreeRoot.Add(Project);
|
||||
else
|
||||
{
|
||||
Explorer.TreeRoot[0] = Project;
|
||||
}
|
||||
});
|
||||
|
||||
this.WhenAnyValue(vm => vm.ActiveFile!.CurrentViewModel).Subscribe(n => PropertiesContext = n);
|
||||
|
||||
UndoRedo.Recording = true;
|
||||
}
|
||||
|
||||
public NodeListViewModel NodeList { get; } = new();
|
||||
public UndoRedoViewModel UndoRedo { get; } = new();
|
||||
public AppStateViewModel AppState { get; }
|
||||
public ExplorerViewModel Explorer { get; }
|
||||
|
||||
public ReactiveCommand<Unit, ProjectViewModel?> LoadProject { get; }
|
||||
public ReactiveCommand<Unit, Unit> SaveProject { get; }
|
||||
public ReactiveCommand<Unit, Unit> SaveProjectAs { get; }
|
||||
public Interaction<bool, string?> OpenProjectFile { get; } = new();
|
||||
public Interaction<Unit, Func<FileViewModel>?> SelectTypeAndFileName { get; } = new();
|
||||
public Interaction<string?, string?> SelectName { get; } = new();
|
||||
|
||||
public NodeGrouper Grouper { get; }
|
||||
|
||||
public ProjectViewModel Project
|
||||
{
|
||||
get => _project;
|
||||
set => this.RaiseAndSetIfChanged(ref _project, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<FileViewModel> Files { get; } = new();
|
||||
|
||||
public FileViewModel? ActiveFile
|
||||
{
|
||||
get => _activeFile;
|
||||
set => this.RaiseAndSetIfChanged(ref _activeFile, value);
|
||||
}
|
||||
|
||||
public object? PropertiesContext
|
||||
{
|
||||
get => _propertiesContext;
|
||||
set => this.RaiseAndSetIfChanged(ref _propertiesContext, value);
|
||||
}
|
||||
|
||||
public DxTexturePreviewViewModel? Preview2dContext
|
||||
{
|
||||
get => _preview2dContext;
|
||||
set => this.RaiseAndSetIfChanged(ref _preview2dContext, value);
|
||||
}
|
||||
|
||||
public DxMeshPreviewViewModel? Preview3dContext
|
||||
{
|
||||
get => _preview3dContext;
|
||||
set => this.RaiseAndSetIfChanged(ref _preview3dContext, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
intromat/Intromat/ViewModels/ModuleViewModel.cs
Normal file
50
intromat/Intromat/ViewModels/ModuleViewModel.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reactive.Linq;
|
||||
using DynamicData;
|
||||
using Intromat.Interfaces;
|
||||
using Intromat.Nodes;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public class ModuleViewModel : FolderViewModel, IFile
|
||||
{
|
||||
private bool _isDirty;
|
||||
|
||||
public ModuleViewModel(ProjectViewModel parent, string name, string fullPath)
|
||||
: base(null, parent, name)
|
||||
{
|
||||
FullPath = fullPath;
|
||||
_isExpanded = true;
|
||||
|
||||
this.WhenAnyValue(vm => vm.Parent)
|
||||
.Buffer(2, 1)
|
||||
.Select(b => (Previous: b[0], Current: b[1]))
|
||||
.Subscribe(t =>
|
||||
{
|
||||
((ProjectViewModel)t.Previous).Modules.Remove(this);
|
||||
((ProjectViewModel)t.Current).Modules.Remove(this);
|
||||
});
|
||||
}
|
||||
|
||||
public static ModuleViewModel CreateDefault(MainViewModel mainVm, ProjectViewModel parent, string fullPath)
|
||||
{
|
||||
var module = new ModuleViewModel(parent, Path.GetFileName(fullPath)!, fullPath);
|
||||
var mainDocument = DocumentViewModel.CreateDefault(mainVm, module, module, "Main");
|
||||
module.Files.Add(mainDocument);
|
||||
module.Files.Add(ShaderFileViewModel.CreateDefault(mainVm, module, module, "Code"));
|
||||
var entryPoint = new MainEntryPointNode { CanBeRemovedByUser = false };
|
||||
mainDocument.MainNetwork.Nodes.Add(entryPoint);
|
||||
return module;
|
||||
}
|
||||
|
||||
public override string FullPath { get; }
|
||||
|
||||
public bool IsDirty
|
||||
{
|
||||
get => _isDirty;
|
||||
set => this.RaiseAndSetIfChanged(ref _isDirty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
33
intromat/Intromat/ViewModels/NetworkBreadcrumbViewModel.cs
Normal file
33
intromat/Intromat/ViewModels/NetworkBreadcrumbViewModel.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using NodeNetwork.Toolkit.BreadcrumbBar;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
internal class CodeGenBreadcrumbViewModel : BreadcrumbViewModel
|
||||
{
|
||||
private ReactiveObject? _vm;
|
||||
|
||||
public ReactiveObject? ViewModel
|
||||
{
|
||||
get => _vm;
|
||||
set => this.RaiseAndSetIfChanged(ref _vm, value);
|
||||
}
|
||||
}
|
||||
internal class CodeGenBreadcrumbViewModel<T> : CodeGenBreadcrumbViewModel where T : ReactiveObject
|
||||
{
|
||||
public new T? ViewModel
|
||||
{
|
||||
get => (T?)base.ViewModel;
|
||||
set => base.ViewModel = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal class NetworkBreadcrumbViewModel : CodeGenBreadcrumbViewModel<NetworkViewModel>
|
||||
{
|
||||
}
|
||||
|
||||
internal class SourceBreadcrumbViewModel : CodeGenBreadcrumbViewModel<CodeGenNodeViewModel>
|
||||
{
|
||||
}
|
||||
}
|
||||
94
intromat/Intromat/ViewModels/Nodes/GroupNodeViewModel.cs
Normal file
94
intromat/Intromat/ViewModels/Nodes/GroupNodeViewModel.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using DynamicData;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.PersistentModel.Nodes;
|
||||
using Intromat.Views;
|
||||
using NodeNetwork.Toolkit.Group.AddEndpointDropPanel;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels.Nodes
|
||||
{
|
||||
public class GroupNodeViewModel : CodeGenNodeViewModel
|
||||
{
|
||||
private CodeNodeGroupIOBinding? _ioBinding;
|
||||
|
||||
static GroupNodeViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new GroupNodeView(), typeof(IViewFor<GroupNodeViewModel>));
|
||||
}
|
||||
|
||||
public GroupNodeViewModel()
|
||||
: base(NodeType.Group)
|
||||
{
|
||||
}
|
||||
|
||||
public NetworkViewModel? Subnet { get; set; }
|
||||
|
||||
public AddEndpointDropPanelViewModel? AddEndpointDropPanelVM { get; private set; }
|
||||
|
||||
public CodeNodeGroupIOBinding? IOBinding
|
||||
{
|
||||
get => _ioBinding;
|
||||
set
|
||||
{
|
||||
if (_ioBinding != null)
|
||||
throw new InvalidOperationException("IOBinding is already set.");
|
||||
|
||||
_ioBinding = value;
|
||||
|
||||
AddEndpointDropPanelVM = new AddEndpointDropPanelViewModel { NodeGroupIOBinding = IOBinding };
|
||||
}
|
||||
}
|
||||
|
||||
public override NodeModelBase CreateModel()
|
||||
{
|
||||
return new GroupModel();
|
||||
}
|
||||
|
||||
public override void SaveModel(NodeModelBase model)
|
||||
{
|
||||
Debug.Assert(Subnet != null, nameof(Subnet) + " != null");
|
||||
Debug.Assert(IOBinding != null, nameof(IOBinding) + " != null");
|
||||
|
||||
base.SaveModel(model);
|
||||
var group = (GroupModel)model;
|
||||
var network = ProjectSerializer.SaveModel((CodeGenNetworkViewModel)Subnet);
|
||||
group.Network = network;
|
||||
foreach (var input in Inputs.Items)
|
||||
{
|
||||
var endpointType = input.GetType().GenericTypeArguments[0];
|
||||
var list = endpointType.IsGenericType && typeof(IObservableList<>) == endpointType.GetGenericTypeDefinition();
|
||||
if (list)
|
||||
endpointType = endpointType.GenericTypeArguments[0];
|
||||
|
||||
group.Inputs.Add(new GroupEndpointModel
|
||||
{
|
||||
Name = input.Name,
|
||||
EndpointType = endpointType.ToString(),
|
||||
SortIndex = input.SortIndex,
|
||||
PortType = ((CodeGenPortViewModel)input.Port).PortType,
|
||||
List = list
|
||||
});
|
||||
}
|
||||
foreach (var output in Outputs.Items)
|
||||
{
|
||||
var endpointType = output.GetType().GenericTypeArguments[0];
|
||||
var list = endpointType.IsGenericType && typeof(IObservableList<>) == endpointType.GetGenericTypeDefinition();
|
||||
if (list)
|
||||
endpointType = endpointType.GenericTypeArguments[0];
|
||||
|
||||
group.Outputs.Add(new GroupEndpointModel
|
||||
{
|
||||
Name = output.Name,
|
||||
EndpointType = endpointType.ToString(),
|
||||
SortIndex = output.SortIndex,
|
||||
PortType = ((CodeGenPortViewModel)output.Port).PortType,
|
||||
List = list
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.PersistentModel.Nodes;
|
||||
using Intromat.Views;
|
||||
using NodeNetwork.Toolkit.Group.AddEndpointDropPanel;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels.Nodes
|
||||
{
|
||||
public class GroupSubnetIONodeViewModel : CodeGenNodeViewModel
|
||||
{
|
||||
private CodeNodeGroupIOBinding? _ioBinding;
|
||||
private bool _isEntranceNode;
|
||||
|
||||
static GroupSubnetIONodeViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new GroupSubnetIONodeView(), typeof(IViewFor<GroupSubnetIONodeViewModel>));
|
||||
}
|
||||
|
||||
public GroupSubnetIONodeViewModel()
|
||||
: base(NodeType.Group)
|
||||
{
|
||||
this.WhenAnyValue(vm => vm.IsEntranceNode)
|
||||
.StartWith(false)
|
||||
.Subscribe(isEntrance => Name = isEntrance ? "Group Input" : "Group Output");
|
||||
}
|
||||
|
||||
public NetworkViewModel? Subnet { get; set; }
|
||||
|
||||
public AddEndpointDropPanelViewModel? AddEndpointDropPanelVM { get; set; }
|
||||
|
||||
public bool IsEntranceNode
|
||||
{
|
||||
get => _isEntranceNode;
|
||||
set => this.RaiseAndSetIfChanged(ref _isEntranceNode, value);
|
||||
}
|
||||
|
||||
public CodeNodeGroupIOBinding? IOBinding
|
||||
{
|
||||
get => _ioBinding;
|
||||
set
|
||||
{
|
||||
if (_ioBinding != null)
|
||||
throw new InvalidOperationException("IOBinding is already set.");
|
||||
|
||||
_ioBinding = value;
|
||||
|
||||
AddEndpointDropPanelVM = new AddEndpointDropPanelViewModel(IsEntranceNode, !IsEntranceNode) { NodeGroupIOBinding = IOBinding };
|
||||
}
|
||||
}
|
||||
|
||||
public override NodeModelBase CreateModel()
|
||||
{
|
||||
return new GroupSubnetIOModel();
|
||||
}
|
||||
|
||||
public override void LoadModel(NodeModelBase model)
|
||||
{
|
||||
base.LoadModel(model);
|
||||
var groupSubnetIO = (GroupSubnetIOModel)model;
|
||||
IsEntranceNode = groupSubnetIO.IsEntrance;
|
||||
}
|
||||
|
||||
public override void SaveModel(NodeModelBase model)
|
||||
{
|
||||
base.SaveModel(model);
|
||||
var groupSubnetIO = (GroupSubnetIOModel)model;
|
||||
groupSubnetIO.IsEntrance = IsEntranceNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.Views.Previews;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels.Previews
|
||||
{
|
||||
public class DxMeshPreviewViewModel : NodePreviewViewModelBase
|
||||
{
|
||||
static DxMeshPreviewViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new DxMeshPreview(), typeof(IViewFor<DxMeshPreviewViewModel>));
|
||||
}
|
||||
|
||||
public DxMeshPreviewViewModel(NodeViewModel parent, IObservable<MeshValue?> textureValue)
|
||||
{
|
||||
MeshValue = textureValue;
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
public IObservable<MeshValue?> MeshValue { get; }
|
||||
|
||||
public NodeViewModel Parent { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.Views.Previews;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels.Previews
|
||||
{
|
||||
public class DxTexturePreviewViewModel : NodePreviewViewModelBase
|
||||
{
|
||||
static DxTexturePreviewViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new DxTexturePreview(), typeof(IViewFor<DxTexturePreviewViewModel>));
|
||||
}
|
||||
|
||||
public DxTexturePreviewViewModel(NodeViewModel parent, IObservable<TextureValue?> textureValue)
|
||||
{
|
||||
TextureValue = textureValue;
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
public IObservable<TextureValue?> TextureValue { get; }
|
||||
|
||||
public NodeViewModel Parent { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Intromat.ViewModels.Previews
|
||||
{
|
||||
public abstract class NodePreviewViewModelBase : ReactiveObject
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Intromat.Views.Previews;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels.Previews
|
||||
{
|
||||
public class StringPreviewViewModel : NodePreviewViewModelBase
|
||||
{
|
||||
private string _value = string.Empty;
|
||||
|
||||
static StringPreviewViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new StringPreview(), typeof(IViewFor<StringPreviewViewModel>));
|
||||
}
|
||||
|
||||
public string Value
|
||||
{
|
||||
get => _value;
|
||||
set => this.RaiseAndSetIfChanged(ref _value, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
66
intromat/Intromat/ViewModels/ProjectViewModel.cs
Normal file
66
intromat/Intromat/ViewModels/ProjectViewModel.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using Intromat.Interfaces;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public class ProjectViewModel : ReactiveObject, IFile, ITreeItem
|
||||
{
|
||||
private string _fullPath;
|
||||
private bool _isDirty;
|
||||
private string _name;
|
||||
private bool _isExpanded;
|
||||
private bool _isSelected;
|
||||
|
||||
public ProjectViewModel(string name, string fullPath)
|
||||
{
|
||||
_name = name;
|
||||
_fullPath = fullPath;
|
||||
TreeRoot = new[] { this };
|
||||
_isExpanded = true;
|
||||
}
|
||||
|
||||
public string FullPath
|
||||
{
|
||||
get => _fullPath;
|
||||
set => this.RaiseAndSetIfChanged(ref _fullPath, value);
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => this.RaiseAndSetIfChanged(ref _name, value);
|
||||
}
|
||||
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => _isExpanded;
|
||||
set => this.RaiseAndSetIfChanged(ref _isExpanded, value);
|
||||
}
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set => this.RaiseAndSetIfChanged(ref _isSelected, value);
|
||||
}
|
||||
|
||||
public ProjectViewModel[] TreeRoot { get; init; }
|
||||
|
||||
public ObservableCollection<ModuleViewModel> Modules { get; } = new();
|
||||
|
||||
public bool IsDirty
|
||||
{
|
||||
get => _isDirty;
|
||||
set => this.RaiseAndSetIfChanged(ref _isDirty, value);
|
||||
}
|
||||
|
||||
public static ProjectViewModel CreateDefault(MainViewModel mainVm, string fullPath, string name = "Project")
|
||||
{
|
||||
var project = new ProjectViewModel(name, Path.GetFullPath(fullPath));
|
||||
var directory = Path.GetDirectoryName(fullPath)!;
|
||||
project.Modules.Add(ModuleViewModel.CreateDefault(mainVm, project, Path.Combine(directory, "Module1")));
|
||||
return project;
|
||||
}
|
||||
}
|
||||
}
|
||||
352
intromat/Intromat/ViewModels/ShaderFileViewModel.cs
Normal file
352
intromat/Intromat/ViewModels/ShaderFileViewModel.cs
Normal file
@@ -0,0 +1,352 @@
|
||||
using Intromat.Views;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public sealed class ShaderFileViewModel : FileViewModel
|
||||
{
|
||||
private string _source = string.Empty;
|
||||
|
||||
static ShaderFileViewModel()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new ShaderFileView(), typeof(IViewFor<ShaderFileViewModel>));
|
||||
}
|
||||
|
||||
public ShaderFileViewModel(MainViewModel mainVm, ModuleViewModel module, FolderViewModel parent, string name)
|
||||
: base(module, parent, name, "hlsl")
|
||||
{
|
||||
}
|
||||
|
||||
public static ShaderFileViewModel CreateDefault(MainViewModel mainVm, ModuleViewModel module, FolderViewModel parent, string name)
|
||||
{
|
||||
var shaderVm = new ShaderFileViewModel(mainVm, module, parent, name);
|
||||
shaderVm.Source = @"
|
||||
// Pixel UberShader
|
||||
int idot(int3 x, int3 y)
|
||||
{
|
||||
int3 tmp = x * y;
|
||||
return tmp.x + tmp.y + tmp.z;
|
||||
}
|
||||
int idot(int4 x, int4 y)
|
||||
{
|
||||
int4 tmp = x * y;
|
||||
return tmp.x + tmp.y + tmp.z + tmp.w;
|
||||
}
|
||||
|
||||
int iround(float x) { return int (round(x)); }
|
||||
int2 iround(float2 x) { return int2(round(x)); }
|
||||
int3 iround(float3 x) { return int3(round(x)); }
|
||||
int4 iround(float4 x) { return int4(round(x)); }
|
||||
|
||||
int itrunc(float x) { return int (trunc(x)); }
|
||||
int2 itrunc(float2 x) { return int2(trunc(x)); }
|
||||
int3 itrunc(float3 x) { return int3(trunc(x)); }
|
||||
int4 itrunc(float4 x) { return int4(trunc(x)); }
|
||||
|
||||
SamplerState samp[8] : register(s0);
|
||||
|
||||
Texture2DArray Tex[8] : register(t0);
|
||||
|
||||
cbuffer PSBlock : register(b0) {
|
||||
int4 color[4];
|
||||
int4 k[4];
|
||||
int4 alphaRef;
|
||||
float4 texdim[8];
|
||||
int4 czbias[2];
|
||||
int4 cindscale[2];
|
||||
int4 cindmtx[6];
|
||||
int4 cfogcolor;
|
||||
int4 cfogi;
|
||||
float4 cfogf[2];
|
||||
float4 czslope;
|
||||
float4 cefbscale;
|
||||
};
|
||||
struct VS_OUTPUT {
|
||||
float4 pos : POSITION;
|
||||
float4 colors_0 : COLOR0;
|
||||
float4 colors_1 : COLOR1;
|
||||
float3 tex[8] : TEXCOORD0;
|
||||
float4 clipPos : TEXCOORD8;
|
||||
};
|
||||
cbuffer UBERBlock : register(b4) {
|
||||
uint bpmem_genmode;
|
||||
uint bpmem_tevorder[8];
|
||||
uint2 bpmem_combiners[16];
|
||||
uint bpmem_tevksel[8];
|
||||
int4 konstLookup[32];
|
||||
float4 debug;
|
||||
};
|
||||
uint bitfieldExtract(uint val, int off, int size) {
|
||||
// This built-in function is only support in OpenGL 4.0 and ES 3.1
|
||||
// Hopefully the shader compiler will get our meaning and emit the right instruction
|
||||
uint mask = uint((1 << size) - 1);
|
||||
return uint(val >> off) & mask;
|
||||
}
|
||||
|
||||
int4 sampleTexture(uint sampler_num, float2 uv) {
|
||||
// This is messy, but DirectX, OpenGl 3.3 and Opengl ES 3.0 doesn't support dynamic indexing of the sampler array
|
||||
// With any luck the shader compiler will optimise this if the hardware supports dynamic indexing.
|
||||
switch(sampler_num & 0x7u) {
|
||||
case 0u: return int4(Tex[0].Sample(samp[0], float3(uv, 0.0)) * 255.0);
|
||||
case 1u: return int4(Tex[1].Sample(samp[1], float3(uv, 0.0)) * 255.0);
|
||||
case 2u: return int4(Tex[2].Sample(samp[2], float3(uv, 0.0)) * 255.0);
|
||||
case 3u: return int4(Tex[3].Sample(samp[3], float3(uv, 0.0)) * 255.0);
|
||||
case 4u: return int4(Tex[4].Sample(samp[4], float3(uv, 0.0)) * 255.0);
|
||||
case 5u: return int4(Tex[5].Sample(samp[5], float3(uv, 0.0)) * 255.0);
|
||||
case 6u: return int4(Tex[6].Sample(samp[6], float3(uv, 0.0)) * 255.0);
|
||||
case 7u: return int4(Tex[7].Sample(samp[7], float3(uv, 0.0)) * 255.0);
|
||||
}
|
||||
}
|
||||
|
||||
void main(
|
||||
out float4 ocol0 : SV_Target0,
|
||||
in float4 rawpos : SV_Position,
|
||||
in float4 colors_0 : COLOR0,
|
||||
in float4 colors_1 : COLOR1
|
||||
,
|
||||
in float3 tex[8] : TEXCOORD0,
|
||||
in float4 clipPos : TEXCOORD8 ) {
|
||||
int3 ColorInput[16];
|
||||
// ColorInput initial state:
|
||||
ColorInput[0] = color[0].rgb;
|
||||
ColorInput[1] = color[0].aaa;
|
||||
ColorInput[2] = color[1].rgb;
|
||||
ColorInput[3] = color[1].aaa;
|
||||
ColorInput[4] = color[2].rgb;
|
||||
ColorInput[5] = color[2].aaa;
|
||||
ColorInput[6] = color[3].rgb;
|
||||
ColorInput[7] = color[3].aaa;
|
||||
ColorInput[8] = int3(0, 0, 0); // TexColor.rgb (uninitilized)
|
||||
ColorInput[9] = int3(0, 0, 0); // TexColor.aaa (uninitilized)
|
||||
ColorInput[10] = int3(0, 0, 0); // RasColor.rgb (uninitilized)
|
||||
ColorInput[11] = int3(0, 0, 0); // RasColor.aaa (uninitilized)
|
||||
ColorInput[12] = int3(255, 255, 255); // One constant
|
||||
ColorInput[13] = int3(128, 128, 128); // Half constant
|
||||
ColorInput[14] = int3(0, 0, 0); // KonstColor.rgb (unititilized)
|
||||
ColorInput[15] = int3(0, 0, 0); // Zero constant
|
||||
|
||||
int AlphaInput[8];
|
||||
// AlphaInput's intial state:
|
||||
AlphaInput[0] = color[0].a;
|
||||
AlphaInput[1] = color[1].a;
|
||||
AlphaInput[2] = color[2].a;
|
||||
AlphaInput[3] = color[3].a;
|
||||
AlphaInput[4] = 0; // TexColor.a (uninitilized)
|
||||
AlphaInput[5] = 0; // RasColor.a (uninitilized)
|
||||
AlphaInput[6] = 0; // KostColor.a (uninitilized)
|
||||
AlphaInput[7] = 0; // Zero constant
|
||||
|
||||
int AlphaBump = 0;
|
||||
int4 icolors_0 = int4(colors_0 * 255.0);
|
||||
int4 icolors_1 = int4(colors_1 * 255.0);
|
||||
int4 TevResult = color[0];
|
||||
|
||||
uint num_stages = bitfieldExtract(bpmem_genmode, 10, 4);
|
||||
// Main tev loop
|
||||
[loop]
|
||||
for(uint stage = 0u; stage < num_stages; stage++)
|
||||
{
|
||||
uint cc = bpmem_combiners[stage].x;
|
||||
uint ac = bpmem_combiners[stage].y;
|
||||
uint order = bpmem_tevorder[stage>>1];
|
||||
if ((stage & 1u) == 1u)
|
||||
order = order >> 12;
|
||||
|
||||
// TODO: Indirect textures
|
||||
|
||||
// Sample texture for stage
|
||||
int4 texColor;
|
||||
if((order & 64u) != 0u) {
|
||||
// Texture is enabled
|
||||
uint sampler_num = bitfieldExtract(order, 0, 3);
|
||||
uint tex_coord = bitfieldExtract(order, 3, 3);
|
||||
|
||||
// TODO: there is an optional perspective divide here (not to mention all of indirect)
|
||||
int2 fixedPoint_uv = itrunc(tex[tex_coord].xy * texdim[sampler_num].zw * 128.0);
|
||||
float2 uv = (float2(fixedPoint_uv) / 128.0) * texdim[sampler_num].xy;
|
||||
|
||||
texColor = sampleTexture(sampler_num, uv);
|
||||
} else {
|
||||
// Texture is disabled
|
||||
texColor = int4(255, 255, 255, 255);
|
||||
}
|
||||
// TODO: color channel swapping
|
||||
ColorInput[8] = texColor.rgb;
|
||||
ColorInput[9] = texColor.aaa;
|
||||
AlphaInput[4] = texColor.a;
|
||||
|
||||
// Set Konst for stage
|
||||
uint tevksel = bpmem_tevksel[stage>>1];
|
||||
int4 konst;
|
||||
if ((stage & 1u) == 0u)
|
||||
konst = int4(konstLookup[bitfieldExtract(tevksel, 4, 5)].rgb, konstLookup[bitfieldExtract(tevksel, 9, 5)].a);
|
||||
else
|
||||
konst = int4(konstLookup[bitfieldExtract(tevksel, 14, 5)].rgb, konstLookup[bitfieldExtract(tevksel, 19, 5)].a);
|
||||
|
||||
ColorInput[14] = konst.rgb;
|
||||
AlphaInput[6] = konst.a;
|
||||
|
||||
// Set Ras for stage
|
||||
int4 ras;
|
||||
switch (bitfieldExtract(order, 7, 3)) {
|
||||
case 0u: // Color 0
|
||||
ras = icolors_0;
|
||||
break;
|
||||
case 1u: // Color 1
|
||||
ras = icolors_1;
|
||||
break;
|
||||
case 5u: // Alpha Bump
|
||||
ras = int4(AlphaBump, AlphaBump, AlphaBump, AlphaBump);
|
||||
break;
|
||||
case 6u: // Normalzied Alpha Bump
|
||||
int normalized = AlphaBump | AlphaBump >> 5;
|
||||
ras = int4(normalized, normalized, normalized, normalized);
|
||||
break;
|
||||
default:
|
||||
ras = int4(0, 0, 0, 0);
|
||||
break;
|
||||
}
|
||||
// TODO: color channel swapping
|
||||
ColorInput[10] = ras.rgb;
|
||||
ColorInput[11] = ras.aaa;
|
||||
AlphaInput[5] = ras.a;
|
||||
|
||||
// Color Combiner
|
||||
{
|
||||
uint a = bitfieldExtract(cc, 12, 4);
|
||||
uint b = bitfieldExtract(cc, 8, 4);
|
||||
uint c = bitfieldExtract(cc, 4, 4);
|
||||
uint d = bitfieldExtract(cc, 0, 4);
|
||||
uint bias = bitfieldExtract(cc, 16, 2);
|
||||
bool op = bool(bitfieldExtract(cc, 18, 1));
|
||||
bool _clamp = bool(bitfieldExtract(cc, 19, 1));
|
||||
uint shift = bitfieldExtract(cc, 20, 2);
|
||||
uint dest = bitfieldExtract(cc, 22, 2);
|
||||
|
||||
int3 A = ColorInput[a] & int3(255, 255, 255);
|
||||
int3 B = ColorInput[b] & int3(255, 255, 255);
|
||||
int3 C = ColorInput[c] & int3(255, 255, 255);
|
||||
int3 D = ColorInput[d]; // 10 bits + sign
|
||||
|
||||
int3 result;
|
||||
if(bias != 3u) { // Normal mode
|
||||
// Lerp A and B with C
|
||||
C += C >> 7; // Scale C from 0..255 to 0..256
|
||||
int3 lerp = (A << 8) + (B - A)*C;
|
||||
if (shift != 3u) {
|
||||
lerp = lerp << shift;
|
||||
lerp = lerp + (op ? 127 : 128);
|
||||
}
|
||||
result = lerp >> 8;
|
||||
|
||||
// Add/Subtract D (and bias)
|
||||
if (bias == 1u) result += 128;
|
||||
else if (bias == 2u) result -= 128;
|
||||
if(!op) // Add
|
||||
result = D + result;
|
||||
else // Subtract
|
||||
result = D - result;
|
||||
|
||||
// Most of the Shift was moved inside the lerp for improved percision
|
||||
// But we still do the divide by 2 here
|
||||
if (shift == 3u)
|
||||
result = result >> 1;
|
||||
} else { // Compare mode
|
||||
// Not implemented
|
||||
result = int3(255, 0, 0);
|
||||
}
|
||||
|
||||
// Clamp result
|
||||
if (_clamp)
|
||||
result = clamp(result, 0, 255);
|
||||
else
|
||||
result = clamp(result, -1024, 1023);
|
||||
|
||||
if (stage == num_stages) { // If this is the last stage
|
||||
// Write result to output
|
||||
TevResult.rgb = result;
|
||||
//break;
|
||||
} else {
|
||||
// Write result to the correct input register of the next stage
|
||||
ColorInput[dest<<1] = result;
|
||||
}
|
||||
}
|
||||
// Alpha Combiner
|
||||
{
|
||||
uint a = bitfieldExtract(ac, 13, 3);
|
||||
uint b = bitfieldExtract(ac, 10, 3);
|
||||
uint c = bitfieldExtract(ac, 7, 3);
|
||||
uint d = bitfieldExtract(ac, 4, 3);
|
||||
uint bias = bitfieldExtract(ac, 16, 2);
|
||||
bool op = bool(bitfieldExtract(ac, 18, 1));
|
||||
bool _clamp = bool(bitfieldExtract(ac, 19, 1));
|
||||
uint shift = bitfieldExtract(ac, 20, 2);
|
||||
uint dest = bitfieldExtract(ac, 22, 2);
|
||||
|
||||
int A = AlphaInput[a] & 255;
|
||||
int B = AlphaInput[b] & 255;
|
||||
int C = AlphaInput[c] & 255;
|
||||
int D = AlphaInput[d]; // 10 bits + sign
|
||||
|
||||
int result;
|
||||
if(bias != 3u) { // Normal mode
|
||||
// Lerp A and B with C
|
||||
C += C >> 7; // Scale C from 0..255 to 0..256
|
||||
int lerp = (A << 8) + (B - A)*C;
|
||||
if (shift != 3u) {
|
||||
lerp = lerp << shift;
|
||||
lerp = lerp + (op ? 127 : 128);
|
||||
}
|
||||
result = lerp >> 8;
|
||||
|
||||
// Add/Subtract D (and bias)
|
||||
if (bias == 1u) result += 128;
|
||||
else if (bias == 2u) result -= 128;
|
||||
if(!op) // Add
|
||||
result = D + result;
|
||||
else // Subtract
|
||||
result = D - result;
|
||||
|
||||
// Most of the Shift was moved inside the lerp for improved percision
|
||||
// But we still do the divide by 2 here
|
||||
if (shift == 3u)
|
||||
result = result >> 1;
|
||||
} else { // Compare mode
|
||||
// Not implemented
|
||||
result = 255;
|
||||
}
|
||||
|
||||
// Clamp result
|
||||
if (_clamp)
|
||||
result = clamp(result, 0, 255);
|
||||
else
|
||||
result = clamp(result, -1024, 1023);
|
||||
|
||||
if (stage == num_stages) { // If this is the last stage
|
||||
// Write result to output
|
||||
TevResult.a = result;
|
||||
} else {
|
||||
// Write result to the correct input register of the next stage
|
||||
AlphaInput[dest] = result;
|
||||
ColorInput[(dest << 1) + 1u] = int3(result, result, result);
|
||||
}
|
||||
}
|
||||
} // Main tev loop
|
||||
|
||||
ocol0 = float4(TevResult) / 255.0;
|
||||
|
||||
}
|
||||
";
|
||||
return shaderVm;
|
||||
}
|
||||
|
||||
public string Source
|
||||
{
|
||||
get => _source;
|
||||
set => this.RaiseAndSetIfChanged(ref _source, value);
|
||||
}
|
||||
|
||||
public override ReactiveObject CurrentViewModel => this;
|
||||
}
|
||||
}
|
||||
39
intromat/Intromat/ViewModels/UndoItem.cs
Normal file
39
intromat/Intromat/ViewModels/UndoItem.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Intromat.Interfaces;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public class UndoItem<TObservable, TValue> : IUndoItem where TObservable : ReactiveObject
|
||||
{
|
||||
private readonly TValue _newValue;
|
||||
private readonly TObservable _observable;
|
||||
private readonly TValue _oldValue;
|
||||
private readonly PropertyInfo _propertyInfo;
|
||||
|
||||
public UndoItem(IFile file, TObservable observable, Expression<Func<TObservable, TValue>> memberLamda, TValue oldValue, TValue newValue)
|
||||
{
|
||||
File = file;
|
||||
_observable = observable;
|
||||
_oldValue = oldValue;
|
||||
_newValue = newValue;
|
||||
if (memberLamda.Body is MemberExpression { Member: PropertyInfo propertyInfo }) _propertyInfo = propertyInfo;
|
||||
Debug.Assert(_propertyInfo != null);
|
||||
}
|
||||
|
||||
public IFile File { get; }
|
||||
|
||||
public void Redo()
|
||||
{
|
||||
_propertyInfo.SetValue(_observable, _newValue);
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
_propertyInfo.SetValue(_observable, _oldValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
intromat/Intromat/ViewModels/UndoItemGroup.cs
Normal file
36
intromat/Intromat/ViewModels/UndoItemGroup.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Intromat.Interfaces;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public class UndoItemGroup : ReactiveObject, IUndoItem
|
||||
{
|
||||
public List<IUndoItem> Items = new();
|
||||
|
||||
public UndoItemGroup(IFile file)
|
||||
{
|
||||
File = file;
|
||||
}
|
||||
|
||||
public IFile File { get; }
|
||||
|
||||
public void Redo()
|
||||
{
|
||||
foreach (var item in Items)
|
||||
item.Redo();
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
foreach (var item in Items.Reverse<IUndoItem>())
|
||||
item.Undo();
|
||||
}
|
||||
|
||||
public void Push(IUndoItem item)
|
||||
{
|
||||
Items.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
144
intromat/Intromat/ViewModels/UndoRedoViewModel.cs
Normal file
144
intromat/Intromat/ViewModels/UndoRedoViewModel.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Input;
|
||||
using DynamicData;
|
||||
using Intromat.Interfaces;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Intromat.ViewModels
|
||||
{
|
||||
public class UndoRedoViewModel : ReactiveObject
|
||||
{
|
||||
private readonly ObservableAsPropertyHelper<IUndoItem?> _currentRedoItem;
|
||||
private readonly ObservableAsPropertyHelper<IUndoItem?> _currentUndoItem;
|
||||
private UndoItemGroup? _currentGroup;
|
||||
|
||||
public UndoRedoViewModel()
|
||||
{
|
||||
GroupStack.Connect().ActOnEveryObject(g => CurrentGroup = g, _ => CurrentGroup = GroupStack.Count > 0 ? GroupStack.Items.ElementAt(GroupStack.Count - 1) : null);
|
||||
|
||||
this.WhenAnyObservable(vm => vm.UndoStack.CountChanged)
|
||||
.Select(_ => UndoStack.Count > 0 ? UndoStack.Items.ElementAt(UndoStack.Count - 1) : null)
|
||||
.ToProperty(this, vm => vm.CurrentUndoItem, out _currentUndoItem);
|
||||
|
||||
this.WhenAnyObservable(vm => vm.RedoStack.CountChanged)
|
||||
.Select(_ => RedoStack.Count > 0 ? RedoStack.Items.ElementAt(RedoStack.Count - 1) : null)
|
||||
.ToProperty(this, vm => vm.CurrentRedoItem, out _currentRedoItem);
|
||||
|
||||
UndoCommand = ReactiveCommand.Create(Undo,
|
||||
this.WhenAnyValue(vm => vm.CurrentUndoItem).Select(i => i != null));
|
||||
RedoCommand = ReactiveCommand.Create(Redo,
|
||||
this.WhenAnyValue(vm => vm.CurrentRedoItem).Select(i => i != null));
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit, Unit> RedoCommand { get; }
|
||||
public ReactiveCommand<Unit, Unit> UndoCommand { get; }
|
||||
|
||||
public ISourceList<UndoItemGroup> GroupStack { get; } = new SourceList<UndoItemGroup>();
|
||||
|
||||
public UndoItemGroup? CurrentGroup
|
||||
{
|
||||
get => _currentGroup;
|
||||
set => this.RaiseAndSetIfChanged(ref _currentGroup, value);
|
||||
}
|
||||
|
||||
public ISourceList<IUndoItem> UndoStack { get; } = new SourceList<IUndoItem>();
|
||||
public IUndoItem? CurrentUndoItem => _currentUndoItem.Value;
|
||||
|
||||
public ISourceList<IUndoItem> RedoStack { get; } = new SourceList<IUndoItem>();
|
||||
public IUndoItem? CurrentRedoItem => _currentRedoItem.Value;
|
||||
|
||||
public bool Recording { get; set; }
|
||||
|
||||
public void Record(IUndoItem item)
|
||||
{
|
||||
if (!Recording)
|
||||
return;
|
||||
|
||||
if (CurrentGroup != null)
|
||||
{
|
||||
CurrentGroup.Push(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
UndoStack.Add(item);
|
||||
RedoStack.Clear();
|
||||
CommandManager.InvalidateRequerySuggested();
|
||||
}
|
||||
}
|
||||
|
||||
public void PushGroup(IFile file)
|
||||
{
|
||||
GroupStack.Add(new UndoItemGroup(file));
|
||||
}
|
||||
|
||||
public void PopGroup()
|
||||
{
|
||||
var group = CurrentGroup;
|
||||
Debug.Assert(group != null);
|
||||
GroupStack.RemoveAt(GroupStack.Count - 1);
|
||||
if (group.Items.Count > 0)
|
||||
Record(group);
|
||||
}
|
||||
|
||||
public void Execute(IUndoItem item)
|
||||
{
|
||||
item.Redo();
|
||||
item.File.IsDirty = true;
|
||||
Record(item);
|
||||
}
|
||||
|
||||
public void Purge(IFile file)
|
||||
{
|
||||
UndoStack.Edit(list =>
|
||||
{
|
||||
for (var i = list.Count - 1; i >= 0; --i)
|
||||
if (list[i].File != file)
|
||||
list.RemoveAt(i);
|
||||
});
|
||||
|
||||
RedoStack.Edit(list =>
|
||||
{
|
||||
for (var i = list.Count - 1; i >= 0; --i)
|
||||
if (list[i].File != file)
|
||||
list.RemoveAt(i);
|
||||
});
|
||||
|
||||
CommandManager.InvalidateRequerySuggested();
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
var item = CurrentUndoItem;
|
||||
Debug.Assert(item != null);
|
||||
UndoStack.RemoveAt(UndoStack.Count - 1);
|
||||
var wasRecording = Recording;
|
||||
if (wasRecording)
|
||||
Recording = false;
|
||||
item.Undo();
|
||||
item.File.IsDirty = true;
|
||||
if (wasRecording)
|
||||
Recording = true;
|
||||
RedoStack.Add(item);
|
||||
CommandManager.InvalidateRequerySuggested();
|
||||
}
|
||||
|
||||
public void Redo()
|
||||
{
|
||||
var item = CurrentRedoItem;
|
||||
Debug.Assert(item != null);
|
||||
RedoStack.RemoveAt(RedoStack.Count - 1);
|
||||
var wasRecording = Recording;
|
||||
if (wasRecording)
|
||||
Recording = false;
|
||||
item.Redo();
|
||||
item.File.IsDirty = true;
|
||||
if (wasRecording)
|
||||
Recording = true;
|
||||
UndoStack.Add(item);
|
||||
CommandManager.InvalidateRequerySuggested();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user