mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-31 09:43:46 +00:00
Node editor - Added connecting pins
This commit is contained in:
parent
86b4258f5d
commit
2907b86174
@ -26,6 +26,7 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public event EventHandler<SingleValueEventArgs<IPin>>? PinConnected;
|
public event EventHandler<SingleValueEventArgs<IPin>>? PinConnected;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public event EventHandler<SingleValueEventArgs<IPin>>? PinDisconnected;
|
public event EventHandler<SingleValueEventArgs<IPin>>? PinDisconnected;
|
||||||
|
|
||||||
@ -79,31 +80,29 @@ namespace Artemis.Core
|
|||||||
public void ConnectTo(IPin pin)
|
public void ConnectTo(IPin pin)
|
||||||
{
|
{
|
||||||
_connectedTo.Add(pin);
|
_connectedTo.Add(pin);
|
||||||
OnPropertyChanged(nameof(ConnectedTo));
|
|
||||||
|
|
||||||
PinConnected?.Invoke(this, new SingleValueEventArgs<IPin>(pin));
|
|
||||||
if (!pin.ConnectedTo.Contains(this))
|
if (!pin.ConnectedTo.Contains(this))
|
||||||
pin.ConnectTo(this);
|
pin.ConnectTo(this);
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(ConnectedTo));
|
||||||
|
PinConnected?.Invoke(this, new SingleValueEventArgs<IPin>(pin));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void DisconnectFrom(IPin pin)
|
public void DisconnectFrom(IPin pin)
|
||||||
{
|
{
|
||||||
_connectedTo.Remove(pin);
|
_connectedTo.Remove(pin);
|
||||||
OnPropertyChanged(nameof(ConnectedTo));
|
|
||||||
|
|
||||||
PinDisconnected?.Invoke(this, new SingleValueEventArgs<IPin>(pin));
|
|
||||||
if (pin.ConnectedTo.Contains(this))
|
if (pin.ConnectedTo.Contains(this))
|
||||||
pin.DisconnectFrom(this);
|
pin.DisconnectFrom(this);
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(ConnectedTo));
|
||||||
|
PinDisconnected?.Invoke(this, new SingleValueEventArgs<IPin>(pin));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void DisconnectAll()
|
public void DisconnectAll()
|
||||||
{
|
{
|
||||||
List<IPin> connectedPins = new(_connectedTo);
|
List<IPin> connectedPins = new(_connectedTo);
|
||||||
|
|
||||||
_connectedTo.Clear();
|
_connectedTo.Clear();
|
||||||
OnPropertyChanged(nameof(ConnectedTo));
|
|
||||||
|
|
||||||
foreach (IPin pin in connectedPins)
|
foreach (IPin pin in connectedPins)
|
||||||
{
|
{
|
||||||
@ -111,6 +110,8 @@ namespace Artemis.Core
|
|||||||
if (pin.ConnectedTo.Contains(this))
|
if (pin.ConnectedTo.Contains(this))
|
||||||
pin.DisconnectFrom(this);
|
pin.DisconnectFrom(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(ConnectedTo));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
14
src/Avalonia/Artemis.UI.Shared/Extensions/PointExtensions.cs
Normal file
14
src/Avalonia/Artemis.UI.Shared/Extensions/PointExtensions.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Avalonia;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared.Extensions
|
||||||
|
{
|
||||||
|
public static class PointExtensions
|
||||||
|
{
|
||||||
|
public static Point Empty = new(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Shared.Services.NodeEditor;
|
||||||
|
|
||||||
|
public class ConnectPins : INodeEditorCommand
|
||||||
|
{
|
||||||
|
private readonly IPin _source;
|
||||||
|
private readonly IPin _target;
|
||||||
|
private readonly List<IPin>? _originalConnections;
|
||||||
|
|
||||||
|
public ConnectPins(IPin source, IPin target)
|
||||||
|
{
|
||||||
|
_source = source;
|
||||||
|
_target = target;
|
||||||
|
|
||||||
|
_originalConnections = _target.Direction == PinDirection.Input ? new List<IPin>(_target.ConnectedTo) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Implementation of INodeEditorCommand
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string DisplayName => "Connect pins";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Execute()
|
||||||
|
{
|
||||||
|
if (_target.Direction == PinDirection.Input)
|
||||||
|
_target.DisconnectAll();
|
||||||
|
_source.ConnectTo(_target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Undo()
|
||||||
|
{
|
||||||
|
_target.DisconnectFrom(_source);
|
||||||
|
|
||||||
|
if (_originalConnections == null)
|
||||||
|
return;
|
||||||
|
foreach (IPin pin in _originalConnections)
|
||||||
|
_target.ConnectTo(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -23,22 +23,22 @@
|
|||||||
</VisualBrush>
|
</VisualBrush>
|
||||||
</Styles.Resources>
|
</Styles.Resources>
|
||||||
|
|
||||||
<!-- Custom controls -->
|
<!-- Custom controls -->
|
||||||
<StyleInclude Source="/Styles/Controls/GradientPicker.axaml" />
|
<StyleInclude Source="/Styles/Controls/GradientPicker.axaml" />
|
||||||
<StyleInclude Source="/Styles/Controls/GradientPickerButton.axaml" />
|
<StyleInclude Source="/Styles/Controls/GradientPickerButton.axaml" />
|
||||||
|
|
||||||
<!-- Custom styles -->
|
<!-- Custom styles -->
|
||||||
<StyleInclude Source="/Styles/Border.axaml" />
|
<StyleInclude Source="/Styles/Border.axaml" />
|
||||||
<StyleInclude Source="/Styles/Button.axaml" />
|
<StyleInclude Source="/Styles/Button.axaml" />
|
||||||
<StyleInclude Source="/Styles/Condensed.axaml" />
|
<StyleInclude Source="/Styles/Condensed.axaml" />
|
||||||
<StyleInclude Source="/Styles/TextBlock.axaml" />
|
<StyleInclude Source="/Styles/TextBlock.axaml" />
|
||||||
<StyleInclude Source="/Styles/Sidebar.axaml" />
|
<StyleInclude Source="/Styles/Sidebar.axaml" />
|
||||||
<StyleInclude Source="/Styles/InfoBar.axaml" />
|
<StyleInclude Source="/Styles/InfoBar.axaml" />
|
||||||
<StyleInclude Source="/Styles/TextBox.axaml" />
|
<StyleInclude Source="/Styles/TextBox.axaml" />
|
||||||
<StyleInclude Source="/Styles/Notifications.axaml" />
|
<StyleInclude Source="/Styles/Notifications.axaml" />
|
||||||
<StyleInclude Source="/Styles/NumberBox.axaml" />
|
<StyleInclude Source="/Styles/NumberBox.axaml" />
|
||||||
<StyleInclude Source="/Styles/TreeView.axaml" />
|
<StyleInclude Source="/Styles/TreeView.axaml" />
|
||||||
|
|
||||||
<Style Selector="Window:windows:windows10 /template/ Border#RootBorder">
|
<Style Selector="Window:windows:windows10 /template/ Border#RootBorder">
|
||||||
<!-- This will show if custom accent color setting is used in Settings page-->
|
<!-- This will show if custom accent color setting is used in Settings page-->
|
||||||
<Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColor}" />
|
<Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColor}" />
|
||||||
@ -49,4 +49,4 @@
|
|||||||
<Setter Property="BorderBrush" Value="#3d3d3d" />
|
<Setter Property="BorderBrush" Value="#3d3d3d" />
|
||||||
<Setter Property="BorderThickness" Value="0 1 0 0" />
|
<Setter Property="BorderThickness" Value="0 1 0 0" />
|
||||||
</Style>
|
</Style>
|
||||||
</Styles>
|
</Styles>
|
||||||
@ -63,6 +63,9 @@
|
|||||||
<Compile Update="Screens\ProfileEditor\Panels\VisualEditor\Visualizers\LayerShapeVisualizerView.axaml.cs">
|
<Compile Update="Screens\ProfileEditor\Panels\VisualEditor\Visualizers\LayerShapeVisualizerView.axaml.cs">
|
||||||
<DependentUpon>LayerShapeVisualizerView.axaml</DependentUpon>
|
<DependentUpon>LayerShapeVisualizerView.axaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Update="Screens\VisualScripting\DragCableView.axaml.cs">
|
||||||
|
<DependentUpon>DragCableView.axaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="DefaultTypes\DataModel\Display\" />
|
<Folder Include="DefaultTypes\DataModel\Display\" />
|
||||||
|
|||||||
@ -95,7 +95,8 @@ namespace Artemis.UI.Ninject.Factories
|
|||||||
NodeScriptViewModel NodeScriptViewModel(NodeScript nodeScript);
|
NodeScriptViewModel NodeScriptViewModel(NodeScript nodeScript);
|
||||||
NodePickerViewModel NodePickerViewModel(NodeScript nodeScript);
|
NodePickerViewModel NodePickerViewModel(NodeScript nodeScript);
|
||||||
NodeViewModel NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node);
|
NodeViewModel NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node);
|
||||||
CableViewModel CableViewModel(NodeScriptViewModel nodeScriptViewModel, IPin? from, IPin? to);
|
CableViewModel CableViewModel(NodeScriptViewModel nodeScriptViewModel, IPin from, IPin to);
|
||||||
|
DragCableViewModel DragCableViewModel(PinViewModel pinViewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface INodePinVmFactory
|
public interface INodePinVmFactory
|
||||||
|
|||||||
@ -11,29 +11,25 @@
|
|||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<converters:ColorToSolidColorBrushConverter x:Key="ColorToSolidColorBrushConverter" />
|
<converters:ColorToSolidColorBrushConverter x:Key="ColorToSolidColorBrushConverter" />
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
<Canvas Name="CableCanvas">
|
<Path Name="CablePath"
|
||||||
<Path Name="CablePath"
|
Stroke="{CompiledBinding CableColor, Converter={StaticResource ColorToSolidColorBrushConverter}}"
|
||||||
Stroke="{CompiledBinding CableColor, Converter={StaticResource ColorToSolidColorBrushConverter}}"
|
StrokeThickness="4"
|
||||||
StrokeThickness="4"
|
StrokeLineCap="Round">
|
||||||
StrokeLineCap="Round">
|
<Path.Transitions>
|
||||||
<Path.Transitions>
|
<Transitions>
|
||||||
<Transitions>
|
<ThicknessTransition Property="Margin" Duration="200"></ThicknessTransition>
|
||||||
<ThicknessTransition Property="Margin" Duration="200"></ThicknessTransition>
|
</Transitions>
|
||||||
</Transitions>
|
</Path.Transitions>
|
||||||
</Path.Transitions>
|
<Path.Data>
|
||||||
<Path.Data>
|
<PathGeometry>
|
||||||
<PathGeometry>
|
<PathGeometry.Figures>
|
||||||
<PathGeometry.Figures>
|
<PathFigure IsClosed="False">
|
||||||
<PathFigure StartPoint="{CompiledBinding FromPoint}" IsClosed="False">
|
<PathFigure.Segments>
|
||||||
<PathFigure.Segments>
|
<BezierSegment />
|
||||||
<BezierSegment Point1="{CompiledBinding FromTargetPoint}"
|
</PathFigure.Segments>
|
||||||
Point2="{CompiledBinding ToTargetPoint}"
|
</PathFigure>
|
||||||
Point3="{CompiledBinding ToPoint}" />
|
</PathGeometry.Figures>
|
||||||
</PathFigure.Segments>
|
</PathGeometry>
|
||||||
</PathFigure>
|
</Path.Data>
|
||||||
</PathGeometry.Figures>
|
</Path>
|
||||||
</PathGeometry>
|
|
||||||
</Path.Data>
|
|
||||||
</Path>
|
|
||||||
</Canvas>
|
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -1,9 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Mixins;
|
using Avalonia.Controls.Mixins;
|
||||||
using Avalonia.Controls.Shapes;
|
using Avalonia.Controls.Shapes;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.Media;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
@ -11,20 +13,38 @@ namespace Artemis.UI.Screens.VisualScripting;
|
|||||||
|
|
||||||
public class CableView : ReactiveUserControl<CableViewModel>
|
public class CableView : ReactiveUserControl<CableViewModel>
|
||||||
{
|
{
|
||||||
|
private const double CABLE_OFFSET = 24 * 4;
|
||||||
|
private readonly Path _cablePath;
|
||||||
|
|
||||||
public CableView()
|
public CableView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
Path cablePath = this.Get<Path>("CablePath");
|
_cablePath = this.Get<Path>("CablePath");
|
||||||
|
|
||||||
// Swap a margin on and off of the cable path to ensure the visual is always invalidated
|
// Not using bindings here to avoid a warnings
|
||||||
// This is a workaround for https://github.com/AvaloniaUI/Avalonia/issues/4748
|
this.WhenActivated(d =>
|
||||||
this.WhenActivated(d => ViewModel.WhenAnyValue(vm => vm.FromPoint)
|
{
|
||||||
.Subscribe(_ => cablePath.Margin = cablePath.Margin == new Thickness(0, 0, 0, 0) ? new Thickness(1, 1, 0, 0) : new Thickness(0, 0, 0, 0))
|
ViewModel.WhenAnyValue(vm => vm.FromPoint).Subscribe(_ => Update()).DisposeWith(d);
|
||||||
.DisposeWith(d));
|
ViewModel.WhenAnyValue(vm => vm.ToPoint).Subscribe(_ => Update()).DisposeWith(d);
|
||||||
|
Update();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
// Workaround for https://github.com/AvaloniaUI/Avalonia/issues/4748
|
||||||
|
_cablePath.Margin = _cablePath.Margin != new Thickness(0, 0, 0, 0) ? new Thickness(0, 0, 0, 0) : new Thickness(1, 1, 0, 0);
|
||||||
|
|
||||||
|
PathFigure pathFigure = ((PathGeometry) _cablePath.Data).Figures.First();
|
||||||
|
BezierSegment segment = (BezierSegment) pathFigure.Segments!.First();
|
||||||
|
pathFigure.StartPoint = ViewModel!.FromPoint;
|
||||||
|
segment.Point1 = new Point(ViewModel.FromPoint.X + CABLE_OFFSET, ViewModel.FromPoint.Y);
|
||||||
|
segment.Point2 = new Point(ViewModel.ToPoint.X - CABLE_OFFSET, ViewModel.ToPoint.Y);
|
||||||
|
segment.Point3 = new Point(ViewModel.ToPoint.X, ViewModel.ToPoint.Y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,76 +1,53 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
|
||||||
using Artemis.UI.Exceptions;
|
using Artemis.UI.Exceptions;
|
||||||
using Artemis.UI.Screens.VisualScripting.Pins;
|
using Artemis.UI.Screens.VisualScripting.Pins;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Extensions;
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.Threading;
|
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
|
using DynamicData.Binding;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.VisualScripting;
|
namespace Artemis.UI.Screens.VisualScripting;
|
||||||
|
|
||||||
public class CableViewModel : ActivatableViewModelBase
|
public class CableViewModel : ActivatableViewModelBase
|
||||||
{
|
{
|
||||||
private const double CABLE_OFFSET = 24 * 4;
|
private readonly ObservableAsPropertyHelper<Color> _cableColor;
|
||||||
|
private readonly ObservableAsPropertyHelper<Point> _fromPoint;
|
||||||
|
private readonly ObservableAsPropertyHelper<Point> _toPoint;
|
||||||
|
|
||||||
private readonly NodeScriptViewModel _nodeScriptViewModel;
|
|
||||||
private PinDirection _dragDirection;
|
|
||||||
private Point _dragPoint;
|
|
||||||
private bool _isDragging;
|
|
||||||
private IPin? _from;
|
|
||||||
private IPin? _to;
|
|
||||||
private PinViewModel? _fromViewModel;
|
private PinViewModel? _fromViewModel;
|
||||||
private PinViewModel? _toViewModel;
|
private PinViewModel? _toViewModel;
|
||||||
private readonly ObservableAsPropertyHelper<Point> _fromPoint;
|
|
||||||
private readonly ObservableAsPropertyHelper<Point> _fromTargetPoint;
|
|
||||||
private readonly ObservableAsPropertyHelper<Point> _toPoint;
|
|
||||||
private readonly ObservableAsPropertyHelper<Point> _toTargetPoint;
|
|
||||||
private readonly ObservableAsPropertyHelper<Color> _cableColor;
|
|
||||||
|
|
||||||
public CableViewModel(NodeScriptViewModel nodeScriptViewModel, IPin? from, IPin? to)
|
public CableViewModel(NodeScriptViewModel nodeScriptViewModel, IPin from, IPin to)
|
||||||
{
|
{
|
||||||
if (from != null && from.Direction != PinDirection.Output)
|
if (from.Direction != PinDirection.Output)
|
||||||
throw new ArtemisUIException("Can only create cables originating from an output pin");
|
throw new ArtemisUIException("Can only create cables originating from an output pin");
|
||||||
if (to != null && to.Direction != PinDirection.Input)
|
if (to.Direction != PinDirection.Input)
|
||||||
throw new ArtemisUIException("Can only create cables targeted to an input pin");
|
throw new ArtemisUIException("Can only create cables targeted to an input pin");
|
||||||
|
|
||||||
_nodeScriptViewModel = nodeScriptViewModel;
|
|
||||||
_from = from;
|
|
||||||
_to = to;
|
|
||||||
|
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
if (From != null)
|
nodeScriptViewModel.PinViewModels.ToObservableChangeSet().Filter(p => ReferenceEquals(p.Pin, from)).Transform(model => FromViewModel = model).Subscribe().DisposeWith(d);
|
||||||
_nodeScriptViewModel.PinViewModels.Connect().Filter(p => 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);
|
||||||
if (To != null)
|
|
||||||
_nodeScriptViewModel.PinViewModels.Connect().Filter(p => p.Pin == To).Transform(model => ToViewModel = model).Subscribe().DisposeWith(d);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
_fromPoint = this.WhenAnyValue(vm => vm.FromViewModel).Select(p => p != null ? p.WhenAnyValue(pvm => pvm.Position) : Observable.Never<Point>()).Switch().ToProperty(this, vm => vm.FromPoint);
|
_fromPoint = this.WhenAnyValue(vm => vm.FromViewModel)
|
||||||
_fromTargetPoint = this.WhenAnyValue(vm => vm.FromPoint).Select(point => new Point(point.X + CABLE_OFFSET, point.Y)).ToProperty(this, vm => vm.FromTargetPoint);
|
.Select(p => p != null ? p.WhenAnyValue(pvm => pvm.Position) : Observable.Never<Point>())
|
||||||
_toPoint = this.WhenAnyValue(vm => vm.ToViewModel).Select(p => p != null ? p.WhenAnyValue(pvm => pvm.Position) : Observable.Never<Point>()).Switch().ToProperty(this, vm => vm.ToPoint);
|
.Switch()
|
||||||
_toTargetPoint = this.WhenAnyValue(vm => vm.ToPoint).Select(point => new Point(point.X - CABLE_OFFSET, point.Y)).ToProperty(this, vm => vm.ToTargetPoint);
|
.ToProperty(this, vm => vm.FromPoint);
|
||||||
_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);
|
_toPoint = this.WhenAnyValue(vm => vm.ToViewModel)
|
||||||
}
|
.Select(p => p != null ? p.WhenAnyValue(pvm => pvm.Position) : Observable.Never<Point>())
|
||||||
|
.Switch()
|
||||||
|
.ToProperty(this, vm => vm.ToPoint);
|
||||||
|
|
||||||
public IPin? From
|
_cableColor = this.WhenAnyValue(vm => vm.FromViewModel, vm => vm.ToViewModel)
|
||||||
{
|
.Select(tuple => tuple.Item1?.PinColor ?? tuple.Item2?.PinColor ?? new Color(255, 255, 255, 255))
|
||||||
get => _from;
|
.ToProperty(this, vm => vm.CableColor);
|
||||||
set => RaiseAndSetIfChanged(ref _from, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IPin? To
|
|
||||||
{
|
|
||||||
get => _to;
|
|
||||||
set => RaiseAndSetIfChanged(ref _to, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PinViewModel? FromViewModel
|
public PinViewModel? FromViewModel
|
||||||
@ -85,44 +62,7 @@ public class CableViewModel : ActivatableViewModelBase
|
|||||||
set => RaiseAndSetIfChanged(ref _toViewModel, value);
|
set => RaiseAndSetIfChanged(ref _toViewModel, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsDragging
|
|
||||||
{
|
|
||||||
get => _isDragging;
|
|
||||||
set => RaiseAndSetIfChanged(ref _isDragging, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PinDirection DragDirection
|
|
||||||
{
|
|
||||||
get => _dragDirection;
|
|
||||||
set => RaiseAndSetIfChanged(ref _dragDirection, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Point DragPoint
|
|
||||||
{
|
|
||||||
get => _dragPoint;
|
|
||||||
set => RaiseAndSetIfChanged(ref _dragPoint, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Point FromPoint => _fromPoint.Value;
|
public Point FromPoint => _fromPoint.Value;
|
||||||
public Point FromTargetPoint => _fromTargetPoint.Value;
|
|
||||||
public Point ToPoint => _toPoint.Value;
|
public Point ToPoint => _toPoint.Value;
|
||||||
public Point ToTargetPoint => _toTargetPoint.Value;
|
|
||||||
public Color CableColor => _cableColor.Value;
|
public Color CableColor => _cableColor.Value;
|
||||||
|
|
||||||
public void StartDrag(PinDirection dragDirection)
|
|
||||||
{
|
|
||||||
IsDragging = true;
|
|
||||||
DragDirection = dragDirection;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool UpdateDrag(Point position, PinViewModel? targetViewModel)
|
|
||||||
{
|
|
||||||
DragPoint = position;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FinishDrag()
|
|
||||||
{
|
|
||||||
IsDragging = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:visualScripting="clr-namespace:Artemis.UI.Screens.VisualScripting"
|
||||||
|
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.VisualScripting.DragCableView"
|
||||||
|
x:DataType="visualScripting:DragCableViewModel"
|
||||||
|
ClipToBounds="False">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<converters:ColorToSolidColorBrushConverter x:Key="ColorToSolidColorBrushConverter" />
|
||||||
|
</UserControl.Resources>
|
||||||
|
<Path Name="CablePath"
|
||||||
|
Stroke="{CompiledBinding PinViewModel.PinColor, Converter={StaticResource ColorToSolidColorBrushConverter}}"
|
||||||
|
StrokeThickness="4"
|
||||||
|
StrokeLineCap="Round">
|
||||||
|
<Path.Data>
|
||||||
|
<PathGeometry>
|
||||||
|
<PathGeometry.Figures>
|
||||||
|
<PathFigure IsClosed="False">
|
||||||
|
<PathFigure.Segments>
|
||||||
|
<BezierSegment />
|
||||||
|
</PathFigure.Segments>
|
||||||
|
</PathFigure>
|
||||||
|
</PathGeometry.Figures>
|
||||||
|
</PathGeometry>
|
||||||
|
</Path.Data>
|
||||||
|
</Path>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Mixins;
|
||||||
|
using Avalonia.Controls.Shapes;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.VisualScripting;
|
||||||
|
|
||||||
|
public class DragCableView : ReactiveUserControl<DragCableViewModel>
|
||||||
|
{
|
||||||
|
private const double CABLE_OFFSET = 24 * 4;
|
||||||
|
private readonly Path _cablePath;
|
||||||
|
|
||||||
|
public DragCableView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
_cablePath = this.Get<Path>("CablePath");
|
||||||
|
|
||||||
|
// Not using bindings here to avoid warnings
|
||||||
|
this.WhenActivated(d =>
|
||||||
|
{
|
||||||
|
ViewModel.WhenAnyValue(vm => vm.FromPoint).Subscribe(_ => Update()).DisposeWith(d);
|
||||||
|
ViewModel.WhenAnyValue(vm => vm.ToPoint).Subscribe(_ => Update()).DisposeWith(d);
|
||||||
|
Update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
PathFigure pathFigure = ((PathGeometry) _cablePath.Data).Figures.First();
|
||||||
|
BezierSegment segment = (BezierSegment) pathFigure.Segments!.First();
|
||||||
|
pathFigure.StartPoint = ViewModel!.FromPoint;
|
||||||
|
segment.Point1 = new Point(ViewModel.FromPoint.X + CABLE_OFFSET, ViewModel.FromPoint.Y);
|
||||||
|
segment.Point2 = new Point(ViewModel.ToPoint.X - CABLE_OFFSET, ViewModel.ToPoint.Y);
|
||||||
|
segment.Point3 = new Point(ViewModel.ToPoint.X, ViewModel.ToPoint.Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
using System.Reactive.Disposables;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Screens.VisualScripting.Pins;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Avalonia;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.VisualScripting;
|
||||||
|
|
||||||
|
public class DragCableViewModel : ActivatableViewModelBase
|
||||||
|
{
|
||||||
|
private Point _dragPoint;
|
||||||
|
|
||||||
|
private ObservableAsPropertyHelper<Point>? _fromPoint;
|
||||||
|
private ObservableAsPropertyHelper<Point>? _toPoint;
|
||||||
|
|
||||||
|
public DragCableViewModel(PinViewModel pinViewModel)
|
||||||
|
{
|
||||||
|
PinViewModel = pinViewModel;
|
||||||
|
|
||||||
|
// If the pin is output, the pin is the from-point and the drag position is the to-point
|
||||||
|
if (PinViewModel.Pin.Direction == PinDirection.Output)
|
||||||
|
{
|
||||||
|
this.WhenActivated(d =>
|
||||||
|
{
|
||||||
|
_fromPoint = PinViewModel.WhenAnyValue(vm => vm.Position).ToProperty(this, vm => vm.FromPoint).DisposeWith(d);
|
||||||
|
});
|
||||||
|
_toPoint = this.WhenAnyValue(vm => vm.DragPoint).ToProperty(this, vm => vm.ToPoint);
|
||||||
|
}
|
||||||
|
// If the pin is input, the pin is the to-point and the drag position is the from-point;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.WhenActivated(d =>
|
||||||
|
{
|
||||||
|
_toPoint = PinViewModel.WhenAnyValue(vm => vm.Position).ToProperty(this, vm => vm.ToPoint).DisposeWith(d);
|
||||||
|
});
|
||||||
|
_fromPoint = this.WhenAnyValue(vm => vm.DragPoint).ToProperty(this, vm => vm.FromPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PinViewModel PinViewModel { get; }
|
||||||
|
public Point FromPoint => _fromPoint?.Value ?? new Point(0, 0);
|
||||||
|
public Point ToPoint => _toPoint?.Value ?? new Point(0, 0);
|
||||||
|
|
||||||
|
public Point DragPoint
|
||||||
|
{
|
||||||
|
get => _dragPoint;
|
||||||
|
set => RaiseAndSetIfChanged(ref _dragPoint, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -40,6 +40,9 @@
|
|||||||
</Transitions>
|
</Transitions>
|
||||||
</Grid.Transitions>
|
</Grid.Transitions>
|
||||||
|
|
||||||
|
<!-- Drag cable, if any -->
|
||||||
|
<ContentControl Content="{CompiledBinding DragViewModel}" ClipToBounds="False"/>
|
||||||
|
|
||||||
<!-- Cables -->
|
<!-- Cables -->
|
||||||
<ItemsControl Items="{CompiledBinding CableViewModels}" ClipToBounds="False">
|
<ItemsControl Items="{CompiledBinding CableViewModels}" ClipToBounds="False">
|
||||||
<ItemsControl.ItemsPanel>
|
<ItemsControl.ItemsPanel>
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Artemis.UI.Shared.Controls;
|
using Artemis.UI.Shared.Controls;
|
||||||
@ -12,76 +11,74 @@ using Avalonia.Interactivity;
|
|||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using Avalonia.VisualTree;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.VisualScripting
|
namespace Artemis.UI.Screens.VisualScripting;
|
||||||
|
|
||||||
|
public class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
|
||||||
{
|
{
|
||||||
public partial class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
|
private readonly Grid _grid;
|
||||||
|
private readonly ItemsControl _nodesContainer;
|
||||||
|
private readonly SelectionRectangle _selectionRectangle;
|
||||||
|
private readonly ZoomBorder _zoomBorder;
|
||||||
|
|
||||||
|
public NodeScriptView()
|
||||||
{
|
{
|
||||||
private readonly ZoomBorder _zoomBorder;
|
InitializeComponent();
|
||||||
private readonly Grid _grid;
|
|
||||||
private readonly ItemsControl _nodesContainer;
|
|
||||||
private readonly SelectionRectangle _selectionRectangle;
|
|
||||||
|
|
||||||
public NodeScriptView()
|
_grid = this.Find<Grid>("ContainerGrid");
|
||||||
{
|
_zoomBorder = this.Find<ZoomBorder>("ZoomBorder");
|
||||||
InitializeComponent();
|
_nodesContainer = this.Find<ItemsControl>("NodesContainer");
|
||||||
|
_selectionRectangle = this.Find<SelectionRectangle>("SelectionRectangle");
|
||||||
|
_zoomBorder.PropertyChanged += ZoomBorderOnPropertyChanged;
|
||||||
|
UpdateZoomBorderBackground();
|
||||||
|
|
||||||
_nodesContainer = this.Find<ItemsControl>("NodesContainer");
|
_grid.AddHandler(PointerReleasedEvent, CanvasOnPointerReleased, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
|
||||||
_zoomBorder = this.Find<ZoomBorder>("ZoomBorder");
|
}
|
||||||
_grid = this.Find<Grid>("ContainerGrid");
|
|
||||||
_selectionRectangle = this.Find<SelectionRectangle>("SelectionRectangle");
|
private void CanvasOnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
_zoomBorder.PropertyChanged += ZoomBorderOnPropertyChanged;
|
{
|
||||||
|
// If the flyout handled the click, update the position of the node picker
|
||||||
|
if (e.Handled && ViewModel != null)
|
||||||
|
ViewModel.NodePickerViewModel.Position = e.GetPosition(_grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ZoomBorderOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Property.Name == nameof(_zoomBorder.Background))
|
||||||
UpdateZoomBorderBackground();
|
UpdateZoomBorderBackground();
|
||||||
|
}
|
||||||
|
|
||||||
_grid?.AddHandler(PointerReleasedEvent, CanvasOnPointerReleased, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
|
private void UpdateZoomBorderBackground()
|
||||||
}
|
{
|
||||||
|
if (_zoomBorder.Background is VisualBrush visualBrush)
|
||||||
|
visualBrush.DestinationRect = new RelativeRect(_zoomBorder.OffsetX * -1, _zoomBorder.OffsetY * -1, 20, 20, RelativeUnit.Absolute);
|
||||||
|
}
|
||||||
|
|
||||||
private void CanvasOnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
// If the flyout handled the click, update the position of the node picker
|
AvaloniaXamlLoader.Load(this);
|
||||||
if (e.Handled && ViewModel != null)
|
}
|
||||||
ViewModel.NodePickerViewModel.Position = e.GetPosition(_grid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ZoomBorderOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
private void ZoomBorder_OnZoomChanged(object sender, ZoomChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Property.Name == nameof(_zoomBorder.Background))
|
UpdateZoomBorderBackground();
|
||||||
UpdateZoomBorderBackground();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateZoomBorderBackground()
|
private void SelectionRectangle_OnSelectionUpdated(object? sender, SelectionRectangleEventArgs e)
|
||||||
{
|
{
|
||||||
if (_zoomBorder.Background is VisualBrush visualBrush)
|
List<ItemContainerInfo> itemContainerInfos = _nodesContainer.ItemContainerGenerator.Containers.Where(c => c.ContainerControl.Bounds.Intersects(e.Rectangle)).ToList();
|
||||||
visualBrush.DestinationRect = new RelativeRect(_zoomBorder.OffsetX * -1, _zoomBorder.OffsetY * -1, 20, 20, RelativeUnit.Absolute);
|
List<NodeViewModel> nodes = itemContainerInfos.Where(c => c.Item is NodeViewModel).Select(c => (NodeViewModel) c.Item).ToList();
|
||||||
}
|
ViewModel?.UpdateNodeSelection(nodes, e.KeyModifiers.HasFlag(KeyModifiers.Shift), e.KeyModifiers.HasFlag(KeyModifiers.Control));
|
||||||
|
}
|
||||||
|
|
||||||
private void InitializeComponent()
|
private void SelectionRectangle_OnSelectionFinished(object? sender, SelectionRectangleEventArgs e)
|
||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
ViewModel?.FinishNodeSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ZoomBorder_OnZoomChanged(object sender, ZoomChangedEventArgs e)
|
private void ZoomBorder_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
{
|
{
|
||||||
UpdateZoomBorderBackground();
|
if (!_selectionRectangle.IsSelecting)
|
||||||
}
|
ViewModel?.ClearNodeSelection();
|
||||||
|
|
||||||
private void SelectionRectangle_OnSelectionUpdated(object? sender, SelectionRectangleEventArgs e)
|
|
||||||
{
|
|
||||||
List<ItemContainerInfo> itemContainerInfos = _nodesContainer.ItemContainerGenerator.Containers.Where(c => c.ContainerControl.Bounds.Intersects(e.Rectangle)).ToList();
|
|
||||||
List<NodeViewModel> nodes = itemContainerInfos.Where(c => c.Item is NodeViewModel).Select(c => (NodeViewModel) c.Item).ToList();
|
|
||||||
ViewModel?.UpdateNodeSelection(nodes, e.KeyModifiers.HasFlag(KeyModifiers.Shift), e.KeyModifiers.HasFlag(KeyModifiers.Control));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SelectionRectangle_OnSelectionFinished(object? sender, SelectionRectangleEventArgs e)
|
|
||||||
{
|
|
||||||
ViewModel?.FinishNodeSelection();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ZoomBorder_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
|
||||||
{
|
|
||||||
if (!_selectionRectangle.IsSelecting)
|
|
||||||
ViewModel?.ClearNodeSelection();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -20,14 +20,17 @@ namespace Artemis.UI.Screens.VisualScripting;
|
|||||||
|
|
||||||
public class NodeScriptViewModel : ActivatableViewModelBase
|
public class NodeScriptViewModel : ActivatableViewModelBase
|
||||||
{
|
{
|
||||||
private readonly INodeVmFactory _nodeVmFactory;
|
|
||||||
private readonly INodeEditorService _nodeEditorService;
|
private readonly INodeEditorService _nodeEditorService;
|
||||||
|
private readonly SourceList<NodeViewModel> _nodeViewModels;
|
||||||
|
private readonly INodeVmFactory _nodeVmFactory;
|
||||||
|
private DragCableViewModel? _dragViewModel;
|
||||||
private List<NodeViewModel>? _initialNodeSelection;
|
private List<NodeViewModel>? _initialNodeSelection;
|
||||||
|
|
||||||
public NodeScriptViewModel(NodeScript nodeScript, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService)
|
public NodeScriptViewModel(NodeScript nodeScript, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService)
|
||||||
{
|
{
|
||||||
_nodeVmFactory = nodeVmFactory;
|
_nodeVmFactory = nodeVmFactory;
|
||||||
_nodeEditorService = nodeEditorService;
|
_nodeEditorService = nodeEditorService;
|
||||||
|
_nodeViewModels = new SourceList<NodeViewModel>();
|
||||||
|
|
||||||
NodeScript = nodeScript;
|
NodeScript = nodeScript;
|
||||||
NodePickerViewModel = _nodeVmFactory.NodePickerViewModel(nodeScript);
|
NodePickerViewModel = _nodeVmFactory.NodePickerViewModel(nodeScript);
|
||||||
@ -44,48 +47,42 @@ public class NodeScriptViewModel : ActivatableViewModelBase
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Create VMs for all nodes
|
// Create VMs for all nodes
|
||||||
NodeViewModels = new ObservableCollection<NodeViewModel>();
|
_nodeViewModels.Connect().Bind(out ReadOnlyObservableCollection<NodeViewModel> nodeViewModels).Subscribe();
|
||||||
foreach (INode nodeScriptNode in NodeScript.Nodes)
|
_nodeViewModels.Edit(l =>
|
||||||
NodeViewModels.Add(_nodeVmFactory.NodeViewModel(this, nodeScriptNode));
|
{
|
||||||
|
foreach (INode nodeScriptNode in NodeScript.Nodes)
|
||||||
|
l.Add(_nodeVmFactory.NodeViewModel(this, nodeScriptNode));
|
||||||
|
});
|
||||||
|
NodeViewModels = nodeViewModels;
|
||||||
|
|
||||||
|
NodeViewModels.ToObservableChangeSet().TransformMany(vm => vm.PinViewModels)
|
||||||
|
.Bind(out ReadOnlyObservableCollection<PinViewModel> pinViewModels)
|
||||||
|
.Subscribe();
|
||||||
|
PinViewModels = pinViewModels;
|
||||||
|
|
||||||
// Observe all outgoing pin connections and create cables for them
|
// Observe all outgoing pin connections and create cables for them
|
||||||
IObservable<IChangeSet<NodeViewModel>> viewModels = NodeViewModels.ToObservableChangeSet();
|
PinViewModels.ToObservableChangeSet()
|
||||||
PinViewModels = viewModels.TransformMany(vm => vm.OutputPinViewModels)
|
.Filter(p => p.Pin.Direction == PinDirection.Output)
|
||||||
.Merge(viewModels.TransformMany(vm => vm.InputPinViewModels))
|
.TransformMany(p => p.Connections)
|
||||||
.Merge(viewModels
|
.Transform(pin => _nodeVmFactory.CableViewModel(this, pin.ConnectedTo.First(), pin))
|
||||||
.TransformMany(vm => vm.OutputPinCollectionViewModels)
|
|
||||||
.TransformMany(vm => vm.PinViewModels))
|
|
||||||
.Merge(viewModels
|
|
||||||
.TransformMany(vm => vm.InputPinCollectionViewModels)
|
|
||||||
.TransformMany(vm => vm.PinViewModels))
|
|
||||||
.AsObservableList();
|
|
||||||
|
|
||||||
PinViewModels.Connect()
|
|
||||||
.Filter(p => p.Pin.Direction == PinDirection.Input && p.Pin.ConnectedTo.Any())
|
|
||||||
.Transform(vm => _nodeVmFactory.CableViewModel(this, vm.Pin.ConnectedTo.First(), vm.Pin)) // The first pin is the originating output pin
|
|
||||||
.Bind(out ReadOnlyObservableCollection<CableViewModel> cableViewModels)
|
.Bind(out ReadOnlyObservableCollection<CableViewModel> cableViewModels)
|
||||||
.Subscribe();
|
.Subscribe();
|
||||||
|
|
||||||
CableViewModels = cableViewModels;
|
CableViewModels = cableViewModels;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IObservableList<PinViewModel> PinViewModels { get; }
|
|
||||||
|
|
||||||
public PinViewModel? GetPinViewModel(IPin pin)
|
|
||||||
{
|
|
||||||
return NodeViewModels
|
|
||||||
.SelectMany(n => n.Pins)
|
|
||||||
.Concat(NodeViewModels.SelectMany(n => n.InputPinCollectionViewModels.SelectMany(c => c.PinViewModels)))
|
|
||||||
.Concat(NodeViewModels.SelectMany(n => n.OutputPinCollectionViewModels.SelectMany(c => c.PinViewModels)))
|
|
||||||
.FirstOrDefault(vm => vm.Pin == pin);
|
|
||||||
}
|
|
||||||
|
|
||||||
public NodeScript NodeScript { get; }
|
public NodeScript NodeScript { get; }
|
||||||
public ObservableCollection<NodeViewModel> NodeViewModels { get; }
|
public ReadOnlyObservableCollection<NodeViewModel> NodeViewModels { get; }
|
||||||
|
public ReadOnlyObservableCollection<PinViewModel> PinViewModels { get; }
|
||||||
public ReadOnlyObservableCollection<CableViewModel> CableViewModels { get; }
|
public ReadOnlyObservableCollection<CableViewModel> CableViewModels { get; }
|
||||||
public NodePickerViewModel NodePickerViewModel { get; }
|
public NodePickerViewModel NodePickerViewModel { get; }
|
||||||
public NodeEditorHistory History { get; }
|
public NodeEditorHistory History { get; }
|
||||||
|
|
||||||
|
public DragCableViewModel? DragViewModel
|
||||||
|
{
|
||||||
|
get => _dragViewModel;
|
||||||
|
set => RaiseAndSetIfChanged(ref _dragViewModel, value);
|
||||||
|
}
|
||||||
|
|
||||||
public void UpdateNodeSelection(List<NodeViewModel> nodes, bool expand, bool invert)
|
public void UpdateNodeSelection(List<NodeViewModel> nodes, bool expand, bool invert)
|
||||||
{
|
{
|
||||||
_initialNodeSelection ??= NodeViewModels.Where(vm => vm.IsSelected).ToList();
|
_initialNodeSelection ??= NodeViewModels.Where(vm => vm.IsSelected).ToList();
|
||||||
@ -147,15 +144,37 @@ public class NodeScriptViewModel : ActivatableViewModelBase
|
|||||||
_nodeEditorService.ExecuteCommand(NodeScript, moveNode);
|
_nodeEditorService.ExecuteCommand(NodeScript, moveNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool UpdatePinDrag(PinViewModel sourcePinViewModel, PinViewModel? targetPinVmModel, Point position)
|
||||||
|
{
|
||||||
|
if (DragViewModel?.PinViewModel != sourcePinViewModel)
|
||||||
|
DragViewModel = new DragCableViewModel(sourcePinViewModel);
|
||||||
|
|
||||||
|
DragViewModel.DragPoint = position;
|
||||||
|
|
||||||
|
return targetPinVmModel == null || targetPinVmModel.IsCompatibleWith(sourcePinViewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FinishPinDrag(PinViewModel sourcePinViewModel, PinViewModel? targetPinVmModel)
|
||||||
|
{
|
||||||
|
if (DragViewModel == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
DragViewModel = null;
|
||||||
|
|
||||||
|
// If dropped on top of a compatible pin, connect to it
|
||||||
|
if (targetPinVmModel != null && targetPinVmModel.IsCompatibleWith(sourcePinViewModel))
|
||||||
|
_nodeEditorService.ExecuteCommand(NodeScript, new ConnectPins(sourcePinViewModel.Pin, targetPinVmModel.Pin));
|
||||||
|
}
|
||||||
|
|
||||||
private void HandleNodeAdded(SingleValueEventArgs<INode> eventArgs)
|
private void HandleNodeAdded(SingleValueEventArgs<INode> eventArgs)
|
||||||
{
|
{
|
||||||
NodeViewModels.Add(_nodeVmFactory.NodeViewModel(this, eventArgs.Value));
|
_nodeViewModels.Add(_nodeVmFactory.NodeViewModel(this, eventArgs.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleNodeRemoved(SingleValueEventArgs<INode> eventArgs)
|
private void HandleNodeRemoved(SingleValueEventArgs<INode> eventArgs)
|
||||||
{
|
{
|
||||||
NodeViewModel? toRemove = NodeViewModels.FirstOrDefault(vm => vm.Node == eventArgs.Value);
|
NodeViewModel? toRemove = NodeViewModels.FirstOrDefault(vm => ReferenceEquals(vm.Node, eventArgs.Value));
|
||||||
if (toRemove != null)
|
if (toRemove != null)
|
||||||
NodeViewModels.Remove(toRemove);
|
_nodeViewModels.Remove(toRemove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,34 +39,44 @@ public class NodeViewModel : ActivatableViewModelBase
|
|||||||
|
|
||||||
SourceList<IPin> nodePins = new();
|
SourceList<IPin> nodePins = new();
|
||||||
SourceList<IPinCollection> nodePinCollections = new();
|
SourceList<IPinCollection> nodePinCollections = new();
|
||||||
nodePins.AddRange(Node.Pins);
|
|
||||||
nodePinCollections.AddRange(Node.PinCollections);
|
|
||||||
|
|
||||||
// Create observable collections split up by direction
|
// Create observable collections split up by direction
|
||||||
nodePins.Connect().Filter(n => n.Direction == PinDirection.Input).Transform(nodePinVmFactory.InputPinViewModel)
|
nodePins.Connect()
|
||||||
.Bind(out ReadOnlyObservableCollection<PinViewModel> inputPins).Subscribe();
|
.Filter(n => n.Direction == PinDirection.Input)
|
||||||
nodePins.Connect().Filter(n => n.Direction == PinDirection.Output).Transform(nodePinVmFactory.OutputPinViewModel)
|
.Transform(nodePinVmFactory.InputPinViewModel)
|
||||||
.Bind(out ReadOnlyObservableCollection<PinViewModel> outputPins).Subscribe();
|
.Bind(out ReadOnlyObservableCollection<PinViewModel> inputPins)
|
||||||
|
.Subscribe();
|
||||||
|
nodePins.Connect()
|
||||||
|
.Filter(n => n.Direction == PinDirection.Output)
|
||||||
|
.Transform(nodePinVmFactory.OutputPinViewModel)
|
||||||
|
.Bind(out ReadOnlyObservableCollection<PinViewModel> outputPins)
|
||||||
|
.Subscribe();
|
||||||
InputPinViewModels = inputPins;
|
InputPinViewModels = inputPins;
|
||||||
OutputPinViewModels = outputPins;
|
OutputPinViewModels = outputPins;
|
||||||
|
|
||||||
// Same again but for pin collections
|
// Same again but for pin collections
|
||||||
nodePinCollections.Connect().Filter(n => n.Direction == PinDirection.Input).Transform(nodePinVmFactory.InputPinCollectionViewModel)
|
nodePinCollections.Connect()
|
||||||
.Bind(out ReadOnlyObservableCollection<PinCollectionViewModel> inputPinCollections).Subscribe();
|
.Filter(n => n.Direction == PinDirection.Input)
|
||||||
nodePinCollections.Connect().Filter(n => n.Direction == PinDirection.Output).Transform(nodePinVmFactory.OutputPinCollectionViewModel)
|
.Transform(nodePinVmFactory.InputPinCollectionViewModel)
|
||||||
.Bind(out ReadOnlyObservableCollection<PinCollectionViewModel> outputPinCollections).Subscribe();
|
.Bind(out ReadOnlyObservableCollection<PinCollectionViewModel> inputPinCollections)
|
||||||
|
.Subscribe();
|
||||||
|
nodePinCollections.Connect()
|
||||||
|
.Filter(n => n.Direction == PinDirection.Output)
|
||||||
|
.Transform(nodePinVmFactory.OutputPinCollectionViewModel)
|
||||||
|
.Bind(out ReadOnlyObservableCollection<PinCollectionViewModel> outputPinCollections)
|
||||||
|
.Subscribe();
|
||||||
InputPinCollectionViewModels = inputPinCollections;
|
InputPinCollectionViewModels = inputPinCollections;
|
||||||
OutputPinCollectionViewModels = outputPinCollections;
|
OutputPinCollectionViewModels = outputPinCollections;
|
||||||
|
|
||||||
// Create a single observable collection containing all pin view models
|
// Create a single observable collection containing all pin view models
|
||||||
InputPinViewModels.ToObservableChangeSet()
|
InputPinViewModels.ToObservableChangeSet()
|
||||||
.Merge(InputPinCollectionViewModels.ToObservableChangeSet().TransformMany(c => c.PinViewModels))
|
|
||||||
.Merge(OutputPinViewModels.ToObservableChangeSet())
|
.Merge(OutputPinViewModels.ToObservableChangeSet())
|
||||||
|
.Merge(InputPinCollectionViewModels.ToObservableChangeSet().TransformMany(c => c.PinViewModels))
|
||||||
.Merge(OutputPinCollectionViewModels.ToObservableChangeSet().TransformMany(c => c.PinViewModels))
|
.Merge(OutputPinCollectionViewModels.ToObservableChangeSet().TransformMany(c => c.PinViewModels))
|
||||||
.Bind(out ReadOnlyObservableCollection<PinViewModel> pins)
|
.Bind(out ReadOnlyObservableCollection<PinViewModel> pins)
|
||||||
.Subscribe();
|
.Subscribe();
|
||||||
|
|
||||||
Pins = pins;
|
PinViewModels = pins;
|
||||||
|
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
@ -98,7 +108,7 @@ public class NodeViewModel : ActivatableViewModelBase
|
|||||||
public ReadOnlyObservableCollection<PinCollectionViewModel> InputPinCollectionViewModels { get; }
|
public ReadOnlyObservableCollection<PinCollectionViewModel> InputPinCollectionViewModels { get; }
|
||||||
public ReadOnlyObservableCollection<PinViewModel> OutputPinViewModels { get; }
|
public ReadOnlyObservableCollection<PinViewModel> OutputPinViewModels { get; }
|
||||||
public ReadOnlyObservableCollection<PinCollectionViewModel> OutputPinCollectionViewModels { get; }
|
public ReadOnlyObservableCollection<PinCollectionViewModel> OutputPinCollectionViewModels { get; }
|
||||||
public ReadOnlyObservableCollection<PinViewModel> Pins { get; }
|
public ReadOnlyObservableCollection<PinViewModel> PinViewModels { get; }
|
||||||
|
|
||||||
public ICustomNodeViewModel? CustomNodeViewModel
|
public ICustomNodeViewModel? CustomNodeViewModel
|
||||||
{
|
{
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</UserControl.Styles>
|
</UserControl.Styles>
|
||||||
<StackPanel Name="PinContainer" Orientation="Horizontal" Spacing="6">
|
<StackPanel Name="PinContainer" Orientation="Horizontal" Spacing="6">
|
||||||
<Border Name="PinPoint" PointerMoved="PinPoint_OnPointerMoved" PointerReleased="PinPoint_OnPointerReleased">
|
<Border Name="PinPoint">
|
||||||
<Border Name="VisualPinPoint" />
|
<Border Name="VisualPinPoint" />
|
||||||
</Border>
|
</Border>
|
||||||
<TextBlock Name="PinName" VerticalAlignment="Center" Text="{CompiledBinding Pin.Name}" />
|
<TextBlock Name="PinName" VerticalAlignment="Center" Text="{CompiledBinding Pin.Name}" />
|
||||||
|
|||||||
@ -1,86 +1,18 @@
|
|||||||
using System.Linq;
|
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.PanAndZoom;
|
|
||||||
using Avalonia.Input;
|
|
||||||
using Avalonia.LogicalTree;
|
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.Media;
|
|
||||||
using Avalonia.ReactiveUI;
|
|
||||||
using Avalonia.VisualTree;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.VisualScripting.Pins
|
namespace Artemis.UI.Screens.VisualScripting.Pins;
|
||||||
|
|
||||||
|
public class InputPinView : PinView
|
||||||
{
|
{
|
||||||
public partial class InputPinView : ReactiveUserControl<PinViewModel>
|
public InputPinView()
|
||||||
{
|
{
|
||||||
private bool _dragging;
|
InitializeComponent();
|
||||||
private readonly Border _pinPoint;
|
InitializePin(this.Get<Border>("PinPoint"));
|
||||||
private Canvas? _container;
|
|
||||||
|
|
||||||
public InputPinView()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
_pinPoint = this.Get<Border>("PinPoint");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeComponent()
|
|
||||||
{
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PinPoint_OnPointerMoved(object? sender, PointerEventArgs e)
|
|
||||||
{
|
|
||||||
ZoomBorder? zoomBorder = this.FindAncestorOfType<ZoomBorder>();
|
|
||||||
PointerPoint point = e.GetCurrentPoint(zoomBorder);
|
|
||||||
if (ViewModel == null || zoomBorder == null || !point.Properties.IsLeftButtonPressed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!_dragging)
|
|
||||||
{
|
|
||||||
e.Pointer.Capture(_pinPoint);
|
|
||||||
// ViewModel.StartDrag();
|
|
||||||
}
|
|
||||||
|
|
||||||
PointerPoint absolutePosition = e.GetCurrentPoint(null);
|
|
||||||
OutputPinView? target = (OutputPinView?) zoomBorder.GetLogicalDescendants().FirstOrDefault(d => d is OutputPinView v && v.TransformedBounds != null && v.TransformedBounds.Value.Contains(absolutePosition.Position));
|
|
||||||
|
|
||||||
// ViewModel.UpdateDrag(point.Position, target?.ViewModel);
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PinPoint_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
|
||||||
{
|
|
||||||
if (!_dragging)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_dragging = false;
|
|
||||||
e.Pointer.Capture(null);
|
|
||||||
// ViewModel.FinishDrag();
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnAttachedToVisualTree(e);
|
|
||||||
_container = this.FindAncestorOfType<Canvas>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Render(DrawingContext context)
|
|
||||||
{
|
|
||||||
base.Render(context);
|
|
||||||
UpdatePosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdatePosition()
|
|
||||||
{
|
|
||||||
if (_container == null || ViewModel == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Matrix? transform = this.TransformToVisual(_container);
|
|
||||||
if (transform != null)
|
|
||||||
ViewModel.Position = new Point(Bounds.Width / 2, Bounds.Height / 2).Transform(transform.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,50 +1,18 @@
|
|||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.PanAndZoom;
|
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.Media;
|
|
||||||
using Avalonia.ReactiveUI;
|
|
||||||
using Avalonia.VisualTree;
|
|
||||||
using ReactiveUI;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.VisualScripting.Pins
|
namespace Artemis.UI.Screens.VisualScripting.Pins;
|
||||||
|
|
||||||
|
public class OutputPinView : PinView
|
||||||
{
|
{
|
||||||
public partial class OutputPinView : ReactiveUserControl<PinViewModel>
|
public OutputPinView()
|
||||||
{
|
{
|
||||||
private Canvas? _container;
|
InitializeComponent();
|
||||||
|
InitializePin(this.Get<Border>("PinPoint"));
|
||||||
|
}
|
||||||
|
|
||||||
public OutputPinView()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
AvaloniaXamlLoader.Load(this);
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Render(DrawingContext context)
|
|
||||||
{
|
|
||||||
base.Render(context);
|
|
||||||
UpdatePosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnAttachedToVisualTree(e);
|
|
||||||
_container = this.FindAncestorOfType<Canvas>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeComponent()
|
|
||||||
{
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdatePosition()
|
|
||||||
{
|
|
||||||
if (_container == null || ViewModel == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Matrix? transform = this.TransformToVisual(_container);
|
|
||||||
if (transform != null)
|
|
||||||
ViewModel.Position = new Point(Bounds.Width / 2, Bounds.Height / 2).Transform(transform.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,90 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.VisualScripting.Pins;
|
||||||
|
|
||||||
|
public class PinView : ReactiveUserControl<PinViewModel>
|
||||||
|
{
|
||||||
|
private bool _dragging;
|
||||||
|
private Canvas? _container;
|
||||||
|
private Border? _pinPoint;
|
||||||
|
|
||||||
|
protected void InitializePin(Border pinPoint)
|
||||||
|
{
|
||||||
|
_pinPoint = pinPoint;
|
||||||
|
_pinPoint.PointerMoved += PinPointOnPointerMoved;
|
||||||
|
_pinPoint.PointerReleased += PinPointOnPointerReleased;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PinPointOnPointerMoved(object? sender, PointerEventArgs e)
|
||||||
|
{
|
||||||
|
if (ViewModel == null || _container == null || _pinPoint == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
NodeScriptViewModel? nodeScriptViewModel = this.FindAncestorOfType<NodeScriptView>()?.ViewModel;
|
||||||
|
PointerPoint point = e.GetCurrentPoint(_container);
|
||||||
|
if (nodeScriptViewModel == null || !point.Properties.IsLeftButtonPressed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_dragging)
|
||||||
|
e.Pointer.Capture(_pinPoint);
|
||||||
|
|
||||||
|
PinViewModel? targetPin = (_container.InputHitTest(point.Position) as Border)?.DataContext as PinViewModel;
|
||||||
|
if (targetPin == ViewModel)
|
||||||
|
targetPin = null;
|
||||||
|
|
||||||
|
_pinPoint.Cursor = new Cursor(nodeScriptViewModel.UpdatePinDrag(ViewModel, targetPin, point.Position) ? StandardCursorType.Hand : StandardCursorType.No);
|
||||||
|
_dragging = true;
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PinPointOnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_dragging || ViewModel == null || _container == null || _pinPoint == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_dragging = false;
|
||||||
|
e.Pointer.Capture(null);
|
||||||
|
|
||||||
|
PointerPoint point = e.GetCurrentPoint(_container);
|
||||||
|
PinViewModel? targetPin = (_container.InputHitTest(point.Position) as Border)?.DataContext as PinViewModel;
|
||||||
|
if (targetPin == ViewModel)
|
||||||
|
targetPin = null;
|
||||||
|
|
||||||
|
this.FindAncestorOfType<NodeScriptView>()?.ViewModel?.FinishPinDrag(ViewModel, targetPin);
|
||||||
|
_pinPoint.Cursor = new Cursor(StandardCursorType.Hand);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Overrides of Visual
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnAttachedToVisualTree(e);
|
||||||
|
_container = this.FindAncestorOfType<Canvas>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Render(DrawingContext context)
|
||||||
|
{
|
||||||
|
base.Render(context);
|
||||||
|
UpdatePosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdatePosition()
|
||||||
|
{
|
||||||
|
if (_container == null || ViewModel == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Matrix? transform = this.TransformToVisual(_container);
|
||||||
|
if (transform != null)
|
||||||
|
ViewModel.Position = new Point(Bounds.Width / 2, Bounds.Height / 2).Transform(transform.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -52,11 +52,16 @@ public abstract class PinViewModel : ActivatableViewModelBase
|
|||||||
set => RaiseAndSetIfChanged(ref _position, value);
|
set => RaiseAndSetIfChanged(ref _position, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsTypeCompatible(Type type)
|
public bool IsCompatibleWith(PinViewModel pinViewModel)
|
||||||
{
|
{
|
||||||
return Pin.Type == type
|
if (pinViewModel.Pin.Direction == Pin.Direction)
|
||||||
|| Pin.Type == typeof(Enum) && type.IsEnum
|
return false;
|
||||||
|
if (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.Input && Pin.Type == typeof(object)
|
||||||
|| Pin.Direction == PinDirection.Output && type == typeof(object);
|
|| Pin.Direction == PinDirection.Output && pinViewModel.Pin.Type == typeof(object);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -40,8 +40,8 @@ namespace Artemis.UI.Screens.Workshop
|
|||||||
|
|
||||||
NodeScript<bool> testScript = new("Test script", "A test script");
|
NodeScript<bool> testScript = new("Test script", "A test script");
|
||||||
INode exitNode = testScript.Nodes.Last();
|
INode exitNode = testScript.Nodes.Last();
|
||||||
exitNode.X = 200;
|
exitNode.X = 300;
|
||||||
exitNode.Y = 100;
|
exitNode.Y = 150;
|
||||||
|
|
||||||
OrNode orNode = new() {X = 100, Y = 100};
|
OrNode orNode = new() {X = 100, Y = 100};
|
||||||
testScript.AddNode(orNode);
|
testScript.AddNode(orNode);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user