1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Nodes - Made types mutable for non-generic pins/collections

This commit is contained in:
Robert 2022-03-24 23:47:40 +01:00
parent 30ec28a9a9
commit f824f16658
29 changed files with 303 additions and 259 deletions

View File

@ -257,7 +257,7 @@ namespace Artemis.Core
/// <summary>
/// Returns a boolean indicating whether the provided type can be used as a <see cref="Numeric" />.
/// </summary>
public static bool IsTypeCompatible(Type type)
public static bool IsTypeCompatible(Type? type)
{
return type == typeof(Numeric) ||
type == typeof(float) ||

View File

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
namespace Artemis.Core
@ -76,7 +78,7 @@ namespace Artemis.Core
internal InputPin(INode node, Type type, string name)
: base(node, name)
{
Type = type;
_type = type;
_value = type.GetDefault();
}
@ -84,6 +86,25 @@ namespace Artemis.Core
#region Methods
/// <summary>
/// Changes the type of this pin, disconnecting any pins that are incompatible with the new type.
/// </summary>
/// <param name="type">The new type of the pin.</param>
public void ChangeType(Type type)
{
if (_type == type)
return;
// Disconnect pins incompatible with the new type
List<IPin> toDisconnect = ConnectedTo.Where(p => !p.IsTypeCompatible(type)).ToList();
foreach (IPin pin in toDisconnect)
DisconnectFrom(pin);
// Change the type
SetAndNotify(ref _type, type, nameof(Type));
Value = type.GetDefault();
}
private void Evaluate()
{
if (Type.IsValueType)
@ -104,7 +125,7 @@ namespace Artemis.Core
#region Properties & Fields
/// <inheritdoc />
public override Type Type { get; }
public override Type Type => _type;
/// <inheritdoc />
public override object? PinValue => Value;
@ -113,6 +134,7 @@ namespace Artemis.Core
public override PinDirection Direction => PinDirection.Input;
private object? _value;
private Type _type;
/// <summary>
/// Gets or sets the value of the input pin

View File

@ -59,12 +59,14 @@ namespace Artemis.Core
/// </summary>
public sealed class InputPinCollection : PinCollection
{
private Type _type;
#region Constructors
internal InputPinCollection(INode node, Type type, string name, int initialCount)
: base(node, name)
{
Type = type;
_type = type;
// Can't do this in the base constructor because the type won't be set yet
for (int i = 0; i < initialCount; i++)
@ -75,6 +77,20 @@ namespace Artemis.Core
#region Methods
/// <summary>
/// Changes the type of this pin, disconnecting any pins that are incompatible with the new type.
/// </summary>
/// <param name="type">The new type of the pin.</param>
public void ChangeType(Type type)
{
if (_type == type)
return;
foreach (IPin pin in Pins)
(pin as InputPin)?.ChangeType(type);
SetAndNotify(ref _type, type, nameof(Type));
}
/// <inheritdoc />
public override IPin CreatePin()
{
@ -89,17 +105,12 @@ namespace Artemis.Core
public override PinDirection Direction => PinDirection.Input;
/// <inheritdoc />
public override Type Type { get; }
/// <summary>
/// Gets an enumerable of the pins in this collection
/// </summary>
public new IEnumerable<InputPin> Pins => base.Pins.Cast<InputPin>();
public override Type Type => _type;
/// <summary>
/// Gets an enumerable of the values of the pins in this collection
/// </summary>
public IEnumerable Values => Pins.Where(p => p.Value != null).Select(p => p.Value);
public IEnumerable Values => Pins.Where(p => p.PinValue != null).Select(p => p.PinValue);
#endregion
}

View File

@ -75,5 +75,12 @@ namespace Artemis.Core
/// Disconnects all pins this pin is connected to
/// </summary>
void DisconnectAll();
/// <summary>
/// Determines whether this pin is compatible with the given type
/// </summary>
/// <param name="type">The type to check for compatibility</param>
/// <returns><see langword="true"/> if the type is compatible, otherwise <see langword="false"/>.</returns>
public bool IsTypeCompatible(Type type);
}
}

View File

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
namespace Artemis.Core
@ -66,16 +68,36 @@ namespace Artemis.Core
internal OutputPin(INode node, Type type, string name)
: base(node, name)
{
Type = type;
_type = type;
_value = type.GetDefault();
}
#endregion
#region Methods
/// <summary>
/// Changes the type of this pin, disconnecting any pins that are incompatible with the new type.
/// </summary>
/// <param name="type">The new type of the pin.</param>
public void ChangeType(Type type)
{
// Disconnect pins incompatible with the new type
List<IPin> toDisconnect = ConnectedTo.Where(p => !p.IsTypeCompatible(type)).ToList();
foreach (IPin pin in toDisconnect)
DisconnectFrom(pin);
// Change the type
SetAndNotify(ref _type, type, nameof(Type));
Value = type.GetDefault();
}
#endregion
#region Properties & Fields
/// <inheritdoc />
public override Type Type { get; }
public override Type Type => _type;
/// <inheritdoc />
public override object? PinValue => Value;
@ -84,6 +106,7 @@ namespace Artemis.Core
public override PinDirection Direction => PinDirection.Output;
private object? _value;
private Type _type;
/// <summary>
/// Gets or sets the value of the output pin

View File

@ -101,6 +101,9 @@ namespace Artemis.Core
/// <inheritdoc />
public void DisconnectAll()
{
if (!_connectedTo.Any())
return;
List<IPin> connectedPins = new(_connectedTo);
_connectedTo.Clear();
@ -114,6 +117,12 @@ namespace Artemis.Core
OnPropertyChanged(nameof(ConnectedTo));
}
/// <inheritdoc />
public bool IsTypeCompatible(Type type) => Type == type
|| Type == typeof(Enum) && type.IsEnum
|| Direction == PinDirection.Input && Type == typeof(object)
|| Direction == PinDirection.Output && type == typeof(object);
#endregion
}
}

View File

@ -7,7 +7,7 @@ using Artemis.Core.Events;
namespace Artemis.Core
{
/// <inheritdoc cref="IPinCollection"/>
public abstract class PinCollection : IPinCollection
public abstract class PinCollection : CorePropertyChanged, IPinCollection
{
#region Constructors

View File

@ -7,6 +7,7 @@ using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.DataModelVisualization.Shared;
using Artemis.UI.Shared.Events;
using Artemis.UI.Shared.Extensions;
using Artemis.UI.Shared.Services.Interfaces;
using Avalonia;
using Avalonia.Controls;
@ -267,8 +268,10 @@ public class DataModelPicker : TemplatedControl
if (selected is DataModelPropertyViewModel property && property.DataModelPath != null)
DataModelPath = new DataModelPath(property.DataModelPath);
if (selected is DataModelListViewModel list && list.DataModelPath != null)
else if (selected is DataModelListViewModel list && list.DataModelPath != null)
DataModelPath = new DataModelPath(list.DataModelPath);
else if (selected is DataModelEventViewModel dataModelEvent && dataModelEvent.DataModelPath != null)
DataModelPath = new DataModelPath(dataModelEvent.DataModelPath);
}
private void UpdateCurrentPath(bool selectCurrentPath)
@ -292,24 +295,8 @@ public class DataModelPicker : TemplatedControl
_currentPathDisplay.Text = string.Join(" ", DataModelPath.Segments.Where(s => s.GetPropertyDescription() != null).Select(s => s.GetPropertyDescription()!.Name));
if (_currentPathDescription != null)
_currentPathDescription.Text = DataModelPath.GetPropertyDescription()?.Description;
if (_currentPathIcon != null)
{
Type? type = DataModelPath.GetPropertyType();
if (type == null)
_currentPathIcon.Kind = MaterialIconKind.QuestionMarkCircle;
else if (type.TypeIsNumber())
_currentPathIcon.Kind = MaterialIconKind.CalculatorVariantOutline;
else if (type.IsEnum)
_currentPathIcon.Kind = MaterialIconKind.FormatListBulletedSquare;
else if (type == typeof(bool))
_currentPathIcon.Kind = MaterialIconKind.CircleHalfFull;
else if (type == typeof(string))
_currentPathIcon.Kind = MaterialIconKind.Text;
else if (type == typeof(SKColor))
_currentPathIcon.Kind = MaterialIconKind.Palette;
else
_currentPathIcon.Kind = MaterialIconKind.Matrix;
}
_currentPathIcon.Kind = DataModelPath.GetPropertyType().GetTypeIcon();
}
}

View File

@ -32,6 +32,12 @@ public class DataModelPickerButton : TemplatedControl
public static readonly StyledProperty<bool> ShowFullPathProperty =
AvaloniaProperty.Register<DataModelPicker, bool>(nameof(ShowFullPath));
/// <summary>
/// Gets or sets a boolean indicating whether the button should show the icon of the first provided filter type.
/// </summary>
public static readonly StyledProperty<bool> ShowTypeIconProperty =
AvaloniaProperty.Register<DataModelPicker, bool>(nameof(ShowTypeIcon));
/// <summary>
/// Gets a boolean indicating whether the data model picker has a value.
/// </summary>
@ -77,6 +83,7 @@ public class DataModelPickerButton : TemplatedControl
private bool _attached;
private bool _flyoutActive;
private Button? _button;
private TextBlock? _label;
private DataModelPickerFlyout? _flyout;
static DataModelPickerButton()
@ -103,6 +110,16 @@ public class DataModelPickerButton : TemplatedControl
set => SetValue(ShowFullPathProperty, value);
}
/// <summary>
/// Gets or sets a boolean indicating whether the button should show the icon of the first provided filter type.
/// </summary>
public bool ShowTypeIcon
{
get => GetValue(ShowTypeIconProperty);
set => SetValue(ShowTypeIconProperty, value);
}
/// <summary>
/// Gets a boolean indicating whether the data model picker has a value.
/// </summary>
@ -212,13 +229,13 @@ public class DataModelPickerButton : TemplatedControl
{
HasValue = DataModelPath != null && DataModelPath.IsValid;
if (_button == null)
if (_button == null || _label == null)
return;
if (!HasValue)
{
ToolTip.SetTip(_button, null);
_button.Content = Placeholder;
_label.Text = Placeholder;
}
else
{
@ -227,7 +244,7 @@ public class DataModelPickerButton : TemplatedControl
formattedPath = string.Join(" ", DataModelPath.Segments.Where(s => s.GetPropertyDescription() != null).Select(s => s.GetPropertyDescription()!.Name));
ToolTip.SetTip(_button, formattedPath);
_button.Content = ShowFullPath
_label.Text = ShowFullPath
? formattedPath
: DataModelPath?.Segments.LastOrDefault()?.GetPropertyDescription()?.Name ?? DataModelPath?.Segments.LastOrDefault()?.Identifier;
}
@ -261,6 +278,7 @@ public class DataModelPickerButton : TemplatedControl
_button.Click -= OnButtonClick;
base.OnApplyTemplate(e);
_button = e.NameScope.Find<Button>("MainButton");
_label = e.NameScope.Find<TextBlock>("MainButtonLabel");
if (_button != null)
_button.Click += OnButtonClick;
UpdateValueDisplay();

View File

@ -0,0 +1,37 @@
using Artemis.Core;
using Material.Icons;
using SkiaSharp;
using System;
namespace Artemis.UI.Shared.Extensions
{
/// <summary>
/// Provides utilities when working with types in UI elements.
/// </summary>
public static class TypeExtensions
{
/// <summary>
/// Finds an appropriate Material Design icon for the given <paramref name="type"/>.
/// </summary>
/// <param name="type">The type to retrieve an icon for.</param>
/// <returns>An appropriate Material Design icon for the given <paramref name="type"/>.</returns>
public static MaterialIconKind GetTypeIcon(this Type? type)
{
if (type == null)
return MaterialIconKind.QuestionMarkCircle;
if (type.TypeIsNumber())
return MaterialIconKind.CalculatorVariantOutline;
if (type.IsEnum)
return MaterialIconKind.FormatListBulletedSquare;
if (type == typeof(bool))
return MaterialIconKind.CircleHalfFull;
if (type == typeof(string))
return MaterialIconKind.Text;
if (type == typeof(SKColor))
return MaterialIconKind.Palette;
if (type.IsAssignableTo(typeof(IDataModelEvent)))
return MaterialIconKind.LightningBolt;
return MaterialIconKind.Matrix;
}
}
}

View File

@ -2,7 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:controls1="clr-namespace:Artemis.UI.Shared.Controls"
xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker">
xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker"
xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.Controls.DataModelPicker">
<Design.PreviewWith>
<Border Padding="50">
<StackPanel Spacing="5">
@ -32,6 +33,9 @@
<gradientPicker:GradientPickerButton></gradientPicker:GradientPickerButton>
<gradientPicker:GradientPickerButton Classes="condensed"></gradientPicker:GradientPickerButton>
<dataModelPicker:DataModelPickerButton></dataModelPicker:DataModelPickerButton>
<dataModelPicker:DataModelPickerButton Classes="condensed"></dataModelPicker:DataModelPickerButton>
</StackPanel>
</Border>
</Design.PreviewWith>
@ -79,4 +83,15 @@
<Setter Property="Margin" Value="4" />
<Setter Property="CornerRadius" Value="2" />
</Style>
<Style Selector="dataModelPicker|DataModelPickerButton.condensed">
<Setter Property="Padding" Value="4 2" />
<Setter Property="FontSize" Value="13" />
<Setter Property="MinHeight" Value="24" />
</Style>
<Style Selector="dataModelPicker|DataModelPickerButton.condensed /template/ controls|Button">
<Setter Property="Padding" Value="6 3 11 3" />
<Setter Property="Height" Value="24" />
</Style>
</Styles>

View File

@ -1,17 +1,18 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.Controls.DataModelPicker"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker">
<Design.PreviewWith>
<Border Padding="20" Width="200">
<StackPanel Spacing="5">
<dataModelPicker:DataModelPickerButton />
<gradientPicker:GradientPickerButton />
</StackPanel>
</Border>
</Design.PreviewWith>
<Style Selector="FlyoutPresenter.data-model-picker-presenter">
<!-- <Setter Property="Padding" Value="0" /> -->
<Setter Property="MaxWidth" Value="1200" />
<Setter Property="MaxHeight" Value="1200" />
<Setter Property="Background" Value="{DynamicResource SolidBackgroundFillColorBaseBrush}" />
@ -28,7 +29,25 @@
<ControlTemplate>
<controls:Button Name="MainButton"
CornerRadius="{TemplateBinding CornerRadius}"
BorderBrush="{TemplateBinding BorderBrush}">
BorderBrush="{TemplateBinding BorderBrush}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
HorizontalContentAlignment="Stretch">
<Grid ColumnDefinitions="*,Auto" HorizontalAlignment="Stretch">
<TextBlock Name="MainButtonLabel"
Grid.Column="0"
VerticalAlignment="Center"
TextAlignment="Left" />
<TextBlock Name="ChevronTextBlock"
Grid.Column="1"
FontFamily="{DynamicResource SymbolThemeFontFamily}"
FontSize="13"
Text="&#xE70D;"
VerticalAlignment="Center"
TextAlignment="Right"
Padding="2 2 2 0"
Margin="5 0" />
</Grid>
</controls:Button>
</ControlTemplate>
</Setter>

View File

@ -99,11 +99,7 @@ namespace Artemis.UI.Ninject.Factories
DragCableViewModel DragCableViewModel(PinViewModel pinViewModel);
InputPinViewModel InputPinViewModel(IPin inputPin);
OutputPinViewModel OutputPinViewModel(IPin outputPin);
}
public interface INodePinVmFactory
{
PinCollectionViewModel InputPinCollectionViewModel(IPinCollection inputPinCollection, NodeScriptViewModel nodeScriptViewModel);
PinCollectionViewModel OutputPinCollectionViewModel(IPinCollection outputPinCollection, NodeScriptViewModel nodeScriptViewModel);
InputPinCollectionViewModel InputPinCollectionViewModel(IPinCollection inputPinCollection, NodeScriptViewModel nodeScriptViewModel);
OutputPinCollectionViewModel OutputPinCollectionViewModel(IPinCollection outputPinCollection, NodeScriptViewModel nodeScriptViewModel);
}
}

View File

@ -1,28 +0,0 @@
using System;
using System.Reflection;
using Artemis.Core;
using Artemis.UI.Screens.VisualScripting.Pins;
using Ninject.Extensions.Factory;
namespace Artemis.UI.Ninject.InstanceProviders;
public class NodePinViewModelInstanceProvider : StandardInstanceProvider
{
protected override Type GetType(MethodInfo methodInfo, object[] arguments)
{
if (methodInfo.ReturnType != typeof(PinCollectionViewModel))
return base.GetType(methodInfo, arguments);
if (arguments[0] is IPinCollection pinCollection)
return CreatePinCollectionViewModelType(pinCollection);
return base.GetType(methodInfo, arguments);
}
private Type CreatePinCollectionViewModelType(IPinCollection pinCollection)
{
if (pinCollection.Direction == PinDirection.Input)
return typeof(InputPinCollectionViewModel<>).MakeGenericType(pinCollection.Type);
return typeof(OutputPinCollectionViewModel<>).MakeGenericType(pinCollection.Type);
}
}

View File

@ -59,7 +59,6 @@ namespace Artemis.UI.Ninject
});
Kernel.Bind<IPropertyVmFactory>().ToFactory(() => new LayerPropertyViewModelInstanceProvider());
Kernel.Bind<INodePinVmFactory>().ToFactory(() => new NodePinViewModelInstanceProvider());
// Bind all UI services as singletons
Kernel.Bind(x =>

View File

@ -6,6 +6,7 @@
xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:skiaSharp="clr-namespace:SkiaSharp;assembly=SkiaSharp"
xmlns:shared="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.VisualScripting.CableView"
x:DataType="visualScripting:CableViewModel"
@ -45,7 +46,8 @@
CornerRadius="3"
Padding="4"
Canvas.Left="{CompiledBinding ValuePoint.X}"
Canvas.Top="{CompiledBinding ValuePoint.Y}">
Canvas.Top="{CompiledBinding ValuePoint.Y}"
IsVisible="{CompiledBinding FromViewModel.Pin.PinValue, Converter={x:Static ObjectConverters.IsNotNull}}">
<ContentControl Content="{CompiledBinding FromViewModel.Pin.PinValue}">
<ContentControl.DataTemplates>
<DataTemplate DataType="skiaSharp:SKColor">

View File

@ -16,14 +16,14 @@ namespace Artemis.UI.Screens.VisualScripting;
public class CableViewModel : ActivatableViewModelBase
{
private readonly ObservableAsPropertyHelper<bool> _connected;
private readonly ObservableAsPropertyHelper<Color> _cableColor;
private readonly ObservableAsPropertyHelper<Point> _fromPoint;
private readonly ObservableAsPropertyHelper<Point> _toPoint;
private readonly ObservableAsPropertyHelper<Point> _valuePoint;
private ObservableAsPropertyHelper<Color>? _cableColor;
private PinViewModel? _fromViewModel;
private PinViewModel? _toViewModel;
public CableViewModel(NodeScriptViewModel nodeScriptViewModel, IPin from, IPin to)
{
if (from.Direction != PinDirection.Output)
@ -36,6 +36,12 @@ public class CableViewModel : ActivatableViewModelBase
{
nodeScriptViewModel.PinViewModels.ToObservableChangeSet().Filter(p => ReferenceEquals(p.Pin, from)).Transform(model => FromViewModel = model).Subscribe().DisposeWith(d);
nodeScriptViewModel.PinViewModels.ToObservableChangeSet().Filter(p => ReferenceEquals(p.Pin, to)).Transform(model => ToViewModel = model).Subscribe().DisposeWith(d);
_cableColor = this.WhenAnyValue(vm => vm.FromViewModel, vm => vm.ToViewModel)
.Select(tuple => tuple.Item1?.WhenAnyValue(p => p.PinColor) ?? tuple.Item2?.WhenAnyValue(p => p.PinColor) ?? Observable.Never<Color>())
.Switch()
.ToProperty(this, vm => vm.CableColor)
.DisposeWith(d);
});
_fromPoint = this.WhenAnyValue(vm => vm.FromViewModel)
@ -51,10 +57,6 @@ public class CableViewModel : ActivatableViewModelBase
tuple.Item1.Y + (tuple.Item2.Y - tuple.Item1.Y) / 2
)).ToProperty(this, vm => vm.ValuePoint);
_cableColor = this.WhenAnyValue(vm => vm.FromViewModel, vm => vm.ToViewModel)
.Select(tuple => tuple.Item1?.PinColor ?? tuple.Item2?.PinColor ?? new Color(255, 255, 255, 255))
.ToProperty(this, vm => vm.CableColor);
// Not a perfect solution but this makes sure the cable never renders at 0,0 (can happen when the cable spawns before the pin ever rendered)
_connected = this.WhenAnyValue(vm => vm.FromPoint, vm => vm.ToPoint)
.Select(tuple => tuple.Item1 != new Point(0, 0) && tuple.Item2 != new Point(0, 0))
@ -78,5 +80,5 @@ public class CableViewModel : ActivatableViewModelBase
public Point FromPoint => _fromPoint.Value;
public Point ToPoint => _toPoint.Value;
public Point ValuePoint => _valuePoint.Value;
public Color CableColor => _cableColor.Value;
public Color CableColor => _cableColor?.Value ?? new Color(255, 255, 255, 255);
}

View File

@ -64,6 +64,7 @@ public class NodeScriptViewModel : ActivatableViewModelBase
PinViewModels.ToObservableChangeSet()
.Filter(p => p.Pin.Direction == PinDirection.Output)
.TransformMany(p => p.Connections)
.Filter(p => p.ConnectedTo.Any())
.Transform(pin => _nodeVmFactory.CableViewModel(this, pin.ConnectedTo.First(), pin))
.Bind(out ReadOnlyObservableCollection<CableViewModel> cableViewModels)
.Subscribe();

View File

@ -33,7 +33,7 @@ public class NodeViewModel : ActivatableViewModelBase
private ObservableAsPropertyHelper<bool>? _hasInputPins;
private ObservableAsPropertyHelper<bool>? _hasOutputPins;
public NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node, INodeVmFactory nodeVmFactory, INodePinVmFactory nodePinVmFactory, INodeEditorService nodeEditorService)
public NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService)
{
NodeScriptViewModel = nodeScriptViewModel;
_nodeEditorService = nodeEditorService;
@ -61,12 +61,12 @@ public class NodeViewModel : ActivatableViewModelBase
// Same again but for pin collections
nodePinCollections.Connect()
.Filter(n => n.Direction == PinDirection.Input)
.Transform(c => nodePinVmFactory.InputPinCollectionViewModel(c, nodeScriptViewModel))
.Transform(c => (PinCollectionViewModel) nodeVmFactory.InputPinCollectionViewModel(c, nodeScriptViewModel))
.Bind(out ReadOnlyObservableCollection<PinCollectionViewModel> inputPinCollections)
.Subscribe();
nodePinCollections.Connect()
.Filter(n => n.Direction == PinDirection.Output)
.Transform(c => nodePinVmFactory.OutputPinCollectionViewModel(c, nodeScriptViewModel))
.Transform(c => (PinCollectionViewModel) nodeVmFactory.OutputPinCollectionViewModel(c, nodeScriptViewModel))
.Bind(out ReadOnlyObservableCollection<PinCollectionViewModel> outputPinCollections)
.Subscribe();
InputPinCollectionViewModels = inputPinCollections;

View File

@ -4,12 +4,12 @@ using Artemis.UI.Shared.Services.NodeEditor;
namespace Artemis.UI.Screens.VisualScripting.Pins;
public class InputPinCollectionViewModel<T> : PinCollectionViewModel
public class InputPinCollectionViewModel : PinCollectionViewModel
{
private readonly INodeVmFactory _nodeVmFactory;
public InputPinCollection<T> InputPinCollection { get; }
public IPinCollection InputPinCollection { get; }
public InputPinCollectionViewModel(InputPinCollection<T> inputPinCollection, NodeScriptViewModel nodeScriptViewModel, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService)
public InputPinCollectionViewModel(IPinCollection inputPinCollection, NodeScriptViewModel nodeScriptViewModel, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService)
: base(inputPinCollection, nodeScriptViewModel, nodeEditorService)
{
_nodeVmFactory = nodeVmFactory;

View File

@ -4,12 +4,12 @@ using Artemis.UI.Shared.Services.NodeEditor;
namespace Artemis.UI.Screens.VisualScripting.Pins;
public class OutputPinCollectionViewModel<T> : PinCollectionViewModel
public class OutputPinCollectionViewModel : PinCollectionViewModel
{
private readonly INodeVmFactory _nodeVmFactory;
public OutputPinCollection<T> OutputPinCollection { get; }
public IPinCollection OutputPinCollection { get; }
public OutputPinCollectionViewModel(OutputPinCollection<T> outputPinCollection, NodeScriptViewModel nodeScriptViewModel, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService)
public OutputPinCollectionViewModel(IPinCollection outputPinCollection, NodeScriptViewModel nodeScriptViewModel, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService)
: base(outputPinCollection, nodeScriptViewModel, nodeEditorService)
{
_nodeVmFactory = nodeVmFactory;

View File

@ -16,17 +16,17 @@ namespace Artemis.UI.Screens.VisualScripting.Pins;
public abstract class PinViewModel : ActivatableViewModelBase
{
private Point _position;
private readonly INodeService _nodeService;
private ReactiveCommand<IPin, Unit>? _removePin;
private Point _position;
private Color _pinColor;
private Color _darkenedPinColor;
protected PinViewModel(IPin pin, INodeService nodeService)
{
_nodeService = nodeService;
Pin = pin;
TypeColorRegistration registration = nodeService.GetTypeColorRegistration(Pin.Type);
PinColor = registration.Color.ToColor();
DarkenedPinColor = registration.DarkenedColor.ToColor();
SourceList<IPin> connectedPins = new();
this.WhenActivated(d =>
{
@ -36,17 +36,35 @@ public abstract class PinViewModel : ActivatableViewModelBase
Observable.FromEventPattern<SingleValueEventArgs<IPin>>(x => Pin.PinDisconnected += x, x => Pin.PinDisconnected -= x)
.Subscribe(e => connectedPins.Remove(e.EventArgs.Value))
.DisposeWith(d);
Pin.WhenAnyValue(p => p.Type).Subscribe(_ => UpdatePinColor()).DisposeWith(d);
});
Connections = connectedPins.Connect().AsObservableList();
connectedPins.AddRange(Pin.ConnectedTo);
}
private void UpdatePinColor()
{
TypeColorRegistration registration = _nodeService.GetTypeColorRegistration(Pin.Type);
PinColor = registration.Color.ToColor();
DarkenedPinColor = registration.DarkenedColor.ToColor();
}
public IObservableList<IPin> Connections { get; }
public IPin Pin { get; }
public Color PinColor { get; }
public Color DarkenedPinColor { get; }
public Color PinColor
{
get => _pinColor;
set => RaiseAndSetIfChanged(ref _pinColor, value);
}
public Color DarkenedPinColor
{
get => _darkenedPinColor;
set => RaiseAndSetIfChanged(ref _darkenedPinColor, value);
}
public Point Position
{
@ -62,14 +80,9 @@ public abstract class PinViewModel : ActivatableViewModelBase
public bool IsCompatibleWith(PinViewModel pinViewModel)
{
if (pinViewModel.Pin.Direction == Pin.Direction)
return false;
if (pinViewModel.Pin.Node == Pin.Node)
if (pinViewModel.Pin.Direction == Pin.Direction || pinViewModel.Pin.Node == Pin.Node)
return false;
return Pin.Type == pinViewModel.Pin.Type
|| Pin.Type == typeof(Enum) && pinViewModel.Pin.Type.IsEnum
|| Pin.Direction == PinDirection.Input && Pin.Type == typeof(object)
|| Pin.Direction == PinDirection.Output && pinViewModel.Pin.Type == typeof(object);
return Pin.IsTypeCompatible(pinViewModel.Pin.Type);
}
}

View File

@ -27,9 +27,9 @@ public class DataModelNodeCustomViewModel : CustomNodeViewModel
return;
Modules = new ObservableCollection<Module>();
if (_node.Script.Context is Profile scriptProfile && scriptProfile.Configuration.Module != null)
if (_node.Script?.Context is Profile scriptProfile && scriptProfile.Configuration.Module != null)
Modules.Add(scriptProfile.Configuration.Module);
else if (_node.Script.Context is ProfileConfiguration profileConfiguration && profileConfiguration.Module != null)
else if (_node.Script?.Context is ProfileConfiguration profileConfiguration && profileConfiguration.Module != null)
Modules.Add(profileConfiguration.Module);
_node.PropertyChanged += NodeOnPropertyChanged;
@ -46,7 +46,7 @@ public class DataModelNodeCustomViewModel : CustomNodeViewModel
set => RaiseAndSetIfChanged(ref _modules, value);
}
public DataModelPath DataModelPath
public DataModelPath? DataModelPath
{
get => _node.DataModelPath;
set
@ -56,10 +56,10 @@ public class DataModelNodeCustomViewModel : CustomNodeViewModel
_node.DataModelPath?.Dispose();
_node.DataModelPath = value;
_node.DataModelPath.Save();
_node.DataModelPath?.Save();
_node.Storage = _node.DataModelPath.Entity;
_node.UpdateOutputPin(false);
_node.Storage = _node.DataModelPath?.Entity;
_node.UpdateOutputPin();
}
}

View File

@ -7,8 +7,10 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.VisualScripting.Nodes.DataModel.CustomViews.DataModelEventNodeCustomView"
x:DataType="customViewModels:DataModelEventNodeCustomViewModel">
<dataModelPicker:DataModelPickerButton DataModelPath="{CompiledBinding DataModelPath}"
<dataModelPicker:DataModelPickerButton Classes="condensed"
DataModelPath="{CompiledBinding DataModelPath}"
Modules="{CompiledBinding Modules}"
ShowDataModelValues="{CompiledBinding ShowDataModelValues.Value}"
FilterTypes="{CompiledBinding FilterTypes}" />
FilterTypes="{CompiledBinding FilterTypes}"
VerticalAlignment="Top"/>
</UserControl>

View File

@ -1,18 +0,0 @@
<UserControl x:Class="Artemis.VisualScripting.Nodes.DataModel.CustomViews.DataModelEventNodeCustomView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Vertical">
<controls:DataModelPicker DataModelPath="{Binding DataModelPath}"
Modules="{Binding Modules}"
ShowFullPath="{Binding ShowFullPaths.Value}"
ShowDataModelValues="{Binding ShowDataModelValues.Value}"
FilterTypes="{Binding FilterTypes}"
ButtonBrush="DarkGoldenrod" />
</StackPanel>
</UserControl>

View File

@ -7,7 +7,8 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.VisualScripting.Nodes.DataModel.CustomViews.DataModelNodeCustomView"
x:DataType="customViewModels:DataModelNodeCustomViewModel">
<dataModelPicker:DataModelPickerButton DataModelPath="{CompiledBinding DataModelPath}"
<dataModelPicker:DataModelPickerButton Classes="condensed"
DataModelPath="{CompiledBinding DataModelPath}"
Modules="{CompiledBinding Modules}"
ShowDataModelValues="{CompiledBinding ShowDataModelValues.Value}" />
</UserControl>

View File

@ -1,14 +0,0 @@
<UserControl x:Class="Artemis.VisualScripting.Nodes.DataModel.CustomViews.DataModelNodeCustomView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<controls:DataModelPicker DataModelPath="{Binding DataModelPath}"
Modules="{Binding Modules}"
ShowFullPath="{Binding ShowFullPaths.Value}"
ShowDataModelValues="{Binding ShowDataModelValues.Value}"
ButtonBrush="#434343" />
</UserControl>

View File

@ -10,22 +10,28 @@ public class DataModelEventNode : Node<DataModelPathEntity, DataModelEventNodeCu
{
private int _currentIndex;
private Type _currentType;
private DataModelPath _dataModelPath;
private DataModelPath? _dataModelPath;
private DateTime _lastTrigger;
private bool _updating;
public DataModelEventNode() : base("Data Model-Event", "Responds to a data model event trigger")
{
_currentType = typeof(object);
CreateCycleValues(typeof(object), 1);
CycleValues = CreateInputPinCollection(typeof(object), "", 0);
Output = CreateOutputPin(typeof(object));
CycleValues.PinAdded += CycleValuesOnPinAdded;
CycleValues.PinRemoved += CycleValuesOnPinRemoved;
CycleValues.Add(CycleValues.CreatePin());
}
public InputPinCollection CycleValues { get; set; }
public OutputPin Output { get; set; }
public INodeScript Script { get; set; }
public INodeScript? Script { get; set; }
public DataModelPath DataModelPath
public InputPinCollection CycleValues { get; }
public OutputPin Output { get; }
public DataModelPath? DataModelPath
{
get => _dataModelPath;
set => SetAndNotify(ref _dataModelPath, value);
@ -43,7 +49,7 @@ public class DataModelEventNode : Node<DataModelPathEntity, DataModelEventNodeCu
public override void Evaluate()
{
object outputValue = null;
object? outputValue = null;
if (DataModelPath?.GetValue() is IDataModelEvent dataModelEvent)
{
if (dataModelEvent.LastTrigger > _lastTrigger)
@ -64,56 +70,29 @@ public class DataModelEventNode : Node<DataModelPathEntity, DataModelEventNodeCu
Output.Value = Output.Type.GetDefault()!;
}
private void CreateCycleValues(Type type, int initialCount)
{
if (CycleValues != null)
{
CycleValues.PinAdded -= CycleValuesOnPinAdded;
CycleValues.PinRemoved -= CycleValuesOnPinRemoved;
foreach (IPin pin in CycleValues)
{
pin.PinConnected -= OnPinConnected;
pin.PinDisconnected -= OnPinDisconnected;
}
RemovePinCollection(CycleValues);
}
CycleValues = CreateInputPinCollection(type, "", initialCount);
CycleValues.PinAdded += CycleValuesOnPinAdded;
CycleValues.PinRemoved += CycleValuesOnPinRemoved;
foreach (IPin pin in CycleValues)
{
pin.PinConnected += OnPinConnected;
pin.PinDisconnected += OnPinDisconnected;
}
}
private void CycleValuesOnPinAdded(object sender, SingleValueEventArgs<IPin> e)
private void CycleValuesOnPinAdded(object? sender, SingleValueEventArgs<IPin> e)
{
e.Value.PinConnected += OnPinConnected;
e.Value.PinDisconnected += OnPinDisconnected;
}
private void CycleValuesOnPinRemoved(object sender, SingleValueEventArgs<IPin> e)
private void CycleValuesOnPinRemoved(object? sender, SingleValueEventArgs<IPin> e)
{
e.Value.PinConnected -= OnPinConnected;
e.Value.PinDisconnected -= OnPinDisconnected;
}
private void OnPinDisconnected(object sender, SingleValueEventArgs<IPin> e)
private void OnPinDisconnected(object? sender, SingleValueEventArgs<IPin> e)
{
ProcessPinDisconnected();
}
private void OnPinConnected(object sender, SingleValueEventArgs<IPin> e)
private void OnPinConnected(object? sender, SingleValueEventArgs<IPin> e)
{
IPin source = e.Value;
IPin target = (IPin) sender;
ProcessPinConnected(source, target);
ProcessPinConnected(e.Value);
}
private void ProcessPinConnected(IPin source, IPin target)
private void ProcessPinConnected(IPin source)
{
if (_updating)
return;
@ -123,17 +102,8 @@ public class DataModelEventNode : Node<DataModelPathEntity, DataModelEventNodeCu
_updating = true;
// No need to change anything if the types haven't changed
if (_currentType == source.Type)
return;
int reconnectIndex = CycleValues.ToList().IndexOf(target);
ChangeCurrentType(source.Type);
if (reconnectIndex != -1)
{
CycleValues.ToList()[reconnectIndex].ConnectTo(source);
source.ConnectTo(CycleValues.ToList()[reconnectIndex]);
}
if (_currentType != source.Type)
ChangeCurrentType(source.Type);
}
finally
{
@ -143,17 +113,8 @@ public class DataModelEventNode : Node<DataModelPathEntity, DataModelEventNodeCu
private void ChangeCurrentType(Type type)
{
CreateCycleValues(type, CycleValues.Count());
List<IPin> oldOutputConnections = Output.ConnectedTo.ToList();
RemovePin(Output);
Output = CreateOutputPin(type);
foreach (IPin oldOutputConnection in oldOutputConnections.Where(o => o.Type.IsAssignableFrom(Output.Type)))
{
oldOutputConnection.DisconnectAll();
oldOutputConnection.ConnectTo(Output);
Output.ConnectTo(oldOutputConnection);
}
CycleValues.ChangeType(type);
Output.ChangeType(type);
_currentType = type;
}
@ -176,10 +137,6 @@ public class DataModelEventNode : Node<DataModelPathEntity, DataModelEventNodeCu
}
}
private void UpdatePinsType(IPin source)
{
}
/// <inheritdoc />
public void Dispose()
{

View File

@ -8,16 +8,17 @@ namespace Artemis.VisualScripting.Nodes.DataModel;
[Node("Data Model-Value", "Outputs a selectable data model value.", "Data Model")]
public class DataModelNode : Node<DataModelPathEntity, DataModelNodeCustomViewModel>, IDisposable
{
private DataModelPath _dataModelPath;
private DataModelPath? _dataModelPath;
public DataModelNode() : base("Data Model", "Outputs a selectable data model value")
{
Output = CreateOutputPin(typeof(object));
}
public INodeScript Script { get; private set; }
public OutputPin Output { get; private set; }
public INodeScript? Script { get; private set; }
public OutputPin Output { get; }
public DataModelPath DataModelPath
public DataModelPath? DataModelPath
{
get => _dataModelPath;
set => SetAndNotify(ref _dataModelPath, value);
@ -33,58 +34,40 @@ public class DataModelNode : Node<DataModelPathEntity, DataModelNodeCustomViewMo
DataModelPath = new DataModelPath(Storage);
DataModelPath.PathValidated += DataModelPathOnPathValidated;
UpdateOutputPin(false);
UpdateOutputPin();
}
public override void Evaluate()
{
if (DataModelPath.IsValid)
{
if (Output == null)
UpdateOutputPin(false);
object pathValue = DataModelPath.GetValue();
if (pathValue == null)
{
if (!Output.Type.IsValueType)
Output.Value = null;
}
else
{
if (Output.Type == typeof(Numeric))
Output.Value = new Numeric(pathValue);
else
Output.Value = pathValue;
}
}
}
public void UpdateOutputPin(bool loadConnections)
{
Type type = DataModelPath?.GetPropertyType();
if (Numeric.IsTypeCompatible(type))
type = typeof(Numeric);
if (Output != null && Output.Type == type)
if (DataModelPath == null || !DataModelPath.IsValid)
return;
if (Output != null)
object? pathValue = DataModelPath.GetValue();
if (pathValue == null)
{
RemovePin(Output);
Output = null;
if (!Output.Type.IsValueType)
Output.Value = null;
}
else
{
Output.Value = Output.Type == typeof(Numeric) ? new Numeric(pathValue) : pathValue;
}
if (type != null)
Output = CreateOutputPin(type);
if (loadConnections && Script is NodeScript nodeScript)
nodeScript.LoadConnections();
}
private void DataModelPathOnPathValidated(object sender, EventArgs e)
public void UpdateOutputPin()
{
Dispatcher.UIThread.InvokeAsync(() => UpdateOutputPin(true));
Type? type = DataModelPath?.GetPropertyType();
if (Numeric.IsTypeCompatible(type))
type = typeof(Numeric);
type ??= typeof(object);
if (Output.Type != type)
Output.ChangeType(type);
}
private void DataModelPathOnPathValidated(object? sender, EventArgs e)
{
Dispatcher.UIThread.InvokeAsync(UpdateOutputPin);
}
/// <inheritdoc />