port from perforce

This commit is contained in:
2026-04-18 22:31:51 +02:00
commit 8d0ab5b7cc
8409 changed files with 3972376 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
namespace Intromat.ViewModels
{
public class AppStateViewModel
{
}
}

View 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)
{
}
}
}

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

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

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

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

View File

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

View 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();
}
}
}

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

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

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

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

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

View 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>
{
}
}

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
using ReactiveUI;
namespace Intromat.ViewModels.Previews
{
public abstract class NodePreviewViewModelBase : ReactiveObject
{
}
}

View File

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

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

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

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

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

View 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();
}
}
}