1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2026-01-01 18:23:32 +00:00

VisualScripting: Fixed Preview; Added auto-fit mode

This commit is contained in:
Darth Affe 2021-08-21 21:48:01 +02:00
parent 405d5b756c
commit 7422a9667d
14 changed files with 372 additions and 86 deletions

View File

@ -15,7 +15,10 @@ namespace Artemis.Core
Type ResultType { get; } Type ResultType { get; }
object? Context { get; set; } object? Context { get; set; }
event EventHandler<INode>? NodeAdded;
event EventHandler<INode>? NodeRemoved;
void Run(); void Run();
void AddNode(INode node); void AddNode(INode node);
void RemoveNode(INode node); void RemoveNode(INode node);

View File

@ -16,6 +16,9 @@ namespace Artemis.Core
bool IsEvaluated { get; set; } bool IsEvaluated { get; set; }
event EventHandler<IPin> PinConnected;
event EventHandler<IPin> PinDisconnected;
void ConnectTo(IPin pin); void ConnectTo(IPin pin);
void DisconnectFrom(IPin pin); void DisconnectFrom(IPin pin);
} }

View File

@ -9,6 +9,9 @@ namespace Artemis.Core
PinDirection Direction { get; } PinDirection Direction { get; }
Type Type { get; } Type Type { get; }
event EventHandler<IPin> PinAdded;
event EventHandler<IPin> PinRemoved;
IPin AddPin(); IPin AddPin();
bool Remove(IPin pin); bool Remove(IPin pin);
} }

View File

@ -1,18 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq;
namespace Artemis.Core namespace Artemis.Core
{ {
public abstract class Node : CorePropertyChanged, INode public abstract class Node : CorePropertyChanged, INode
{ {
public event EventHandler Resetting;
#region Properties & Fields #region Properties & Fields
private string _name; private string _name;
public string Name public string Name
{ {
get => _name; get => _name;
@ -20,7 +16,6 @@ namespace Artemis.Core
} }
private string _description; private string _description;
public string Description public string Description
{ {
get => _description; get => _description;
@ -28,7 +23,6 @@ namespace Artemis.Core
} }
private double _x; private double _x;
public double X public double X
{ {
get => _x; get => _x;
@ -36,7 +30,6 @@ namespace Artemis.Core
} }
private double _y; private double _y;
public double Y public double Y
{ {
get => _y; get => _y;
@ -44,7 +37,6 @@ namespace Artemis.Core
} }
private object? _storage; private object? _storage;
public object? Storage public object? Storage
{ {
get => _storage; get => _storage;
@ -61,11 +53,16 @@ namespace Artemis.Core
#endregion #endregion
#region Events
public event EventHandler Resetting;
#endregion
#region Construtors #region Construtors
protected Node() protected Node()
{ { }
}
protected Node(string name, string description) protected Node(string name, string description)
{ {
@ -138,8 +135,7 @@ namespace Artemis.Core
} }
public virtual void Initialize(INodeScript script) public virtual void Initialize(INodeScript script)
{ { }
}
public abstract void Evaluate(); public abstract void Evaluate();
@ -154,12 +150,10 @@ namespace Artemis.Core
public abstract class Node<T> : CustomViewModelNode where T : ICustomNodeViewModel public abstract class Node<T> : CustomViewModelNode where T : ICustomNodeViewModel
{ {
protected Node() protected Node()
{ { }
}
protected Node(string name, string description) : base(name, description) protected Node(string name, string description) : base(name, description)
{ { }
}
public override Type CustomViewModelType => typeof(T); public override Type CustomViewModelType => typeof(T);
public T CustomViewModel => (T) BaseCustomViewModel!; public T CustomViewModel => (T) BaseCustomViewModel!;
@ -169,13 +163,11 @@ namespace Artemis.Core
{ {
/// <inheritdoc /> /// <inheritdoc />
protected CustomViewModelNode() protected CustomViewModelNode()
{ { }
}
/// <inheritdoc /> /// <inheritdoc />
protected CustomViewModelNode(string name, string description) : base(name, description) protected CustomViewModelNode(string name, string description) : base(name, description)
{ { }
}
public abstract Type CustomViewModelType { get; } public abstract Type CustomViewModelType { get; }
public object? BaseCustomViewModel { get; set; } public object? BaseCustomViewModel { get; set; }

View File

@ -28,6 +28,13 @@ namespace Artemis.Core
#endregion #endregion
#region Events
public event EventHandler<INode>? NodeAdded;
public event EventHandler<INode>? NodeRemoved;
#endregion
#region Constructors #region Constructors
public NodeScript(string name, string description, object? context = null) public NodeScript(string name, string description, object? context = null)
@ -67,11 +74,15 @@ namespace Artemis.Core
public void AddNode(INode node) public void AddNode(INode node)
{ {
_nodes.Add(node); _nodes.Add(node);
NodeAdded?.Invoke(this, node);
} }
public void RemoveNode(INode node) public void RemoveNode(INode node)
{ {
_nodes.Remove(node); _nodes.Remove(node);
NodeRemoved?.Invoke(this, node);
} }
public void Dispose() public void Dispose()

View File

@ -27,6 +27,13 @@ namespace Artemis.Core
#endregion #endregion
#region Events
public event EventHandler<IPin> PinConnected;
public event EventHandler<IPin> PinDisconnected;
#endregion
#region Constructors #region Constructors
protected Pin(INode node, string name = "") protected Pin(INode node, string name = "")
@ -46,18 +53,27 @@ namespace Artemis.Core
{ {
_connectedTo.Add(pin); _connectedTo.Add(pin);
OnPropertyChanged(nameof(ConnectedTo)); OnPropertyChanged(nameof(ConnectedTo));
PinConnected?.Invoke(this, pin);
} }
public void DisconnectFrom(IPin pin) public void DisconnectFrom(IPin pin)
{ {
_connectedTo.Remove(pin); _connectedTo.Remove(pin);
OnPropertyChanged(nameof(ConnectedTo)); OnPropertyChanged(nameof(ConnectedTo));
PinDisconnected?.Invoke(this, pin);
} }
public void DisconnectAll() public void DisconnectAll()
{ {
List<IPin> connectedPins = new(_connectedTo);
_connectedTo.Clear(); _connectedTo.Clear();
OnPropertyChanged(nameof(ConnectedTo)); OnPropertyChanged(nameof(ConnectedTo));
foreach (IPin pin in connectedPins)
PinDisconnected?.Invoke(this, pin);
} }
private void OnNodeResetting(object sender, EventArgs e) private void OnNodeResetting(object sender, EventArgs e)

View File

@ -20,6 +20,13 @@ namespace Artemis.Core
#endregion #endregion
#region Events
public event EventHandler<IPin> PinAdded;
public event EventHandler<IPin> PinRemoved;
#endregion
#region Constructors #region Constructors
protected PinCollection(INode node, string name, int initialCount) protected PinCollection(INode node, string name, int initialCount)
@ -35,14 +42,26 @@ namespace Artemis.Core
#region Methods #region Methods
public IPin AddPin() public IPin AddPin()
{ {
IPin pin = CreatePin(); IPin pin = CreatePin();
_pins.Add(pin); _pins.Add(pin);
PinAdded?.Invoke(this, pin);
return pin; return pin;
} }
public bool Remove(IPin pin) => _pins.Remove(pin); public bool Remove(IPin pin)
{
bool removed = _pins.Remove(pin);
if (removed)
PinRemoved?.Invoke(this, pin);
return removed;
}
protected abstract IPin CreatePin(); protected abstract IPin CreatePin();

View File

@ -43,8 +43,9 @@
<Separator Grid.Row="1" Grid.Column="0" Style="{StaticResource MaterialDesignDarkSeparator}" Margin="-2 0" /> <Separator Grid.Row="1" Grid.Column="0" Style="{StaticResource MaterialDesignDarkSeparator}" Margin="-2 0" />
<Grid Grid.Row="2" Grid.Column="0" MouseUp="{s:Action ScriptGridMouseUp}" Cursor="Hand"> <Grid Grid.Row="2" Grid.Column="0" MouseUp="{s:Action ScriptGridMouseUp}" Cursor="Hand">
<controls:VisualScriptEditor Script="{Binding RenderProfileElement.DisplayCondition}" <controls:VisualScriptPresenter Script="{Binding RenderProfileElement.DisplayCondition}"
Visibility="{Binding RenderProfileElement.DisplayCondition, Converter={StaticResource NullToVisibilityConverter}}" /> AutoFitScript="True"
Visibility="{Binding RenderProfileElement.DisplayCondition, Converter={StaticResource NullToVisibilityConverter}}" />
<Border Opacity="0"> <Border Opacity="0">
<Border.Background> <Border.Background>
<SolidColorBrush Color="{Binding Color, Source={StaticResource MaterialDesignCardBackground}}" Opacity="0.75" /> <SolidColorBrush Color="{Binding Color, Source={StaticResource MaterialDesignCardBackground}}" Opacity="0.75" />

View File

@ -7,10 +7,6 @@ namespace Artemis.VisualScripting.Editor.Controls
{ {
public class VisualScriptEditor : Control public class VisualScriptEditor : Control
{ {
#region Properties & Fields
#endregion
#region Dependency Properties #region Dependency Properties
public static readonly DependencyProperty ScriptProperty = DependencyProperty.Register( public static readonly DependencyProperty ScriptProperty = DependencyProperty.Register(
@ -32,9 +28,5 @@ namespace Artemis.VisualScripting.Editor.Controls
} }
#endregion #endregion
#region Methods
#endregion
} }
} }

View File

@ -6,6 +6,7 @@ using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Threading;
using Artemis.Core; using Artemis.Core;
using Artemis.VisualScripting.Editor.Controls.Wrapper; using Artemis.VisualScripting.Editor.Controls.Wrapper;
using Artemis.VisualScripting.ViewModel; using Artemis.VisualScripting.ViewModel;
@ -32,11 +33,13 @@ namespace Artemis.VisualScripting.Editor.Controls
#region Properties & Fields #region Properties & Fields
private bool _fitPending = false;
private Canvas _canvas; private Canvas _canvas;
private ItemsControl _nodeList; private ItemsControl _nodeList;
private ItemsControl _cableList; private ItemsControl _cableList;
private Border _selectionBorder; private Border _selectionBorder;
private TranslateTransform _canvasViewPortTransform; private TranslateTransform _canvasViewPortTransform;
private ScaleTransform _canvasViewPortScale;
private Panel _creationBoxParent; private Panel _creationBoxParent;
private Vector _viewportCenter = new(0, 0); private Vector _viewportCenter = new(0, 0);
@ -121,7 +124,7 @@ namespace Artemis.VisualScripting.Editor.Controls
} }
public static readonly DependencyProperty GridSizeProperty = DependencyProperty.Register( public static readonly DependencyProperty GridSizeProperty = DependencyProperty.Register(
"GridSize", typeof(int), typeof(VisualScriptPresenter), new PropertyMetadata(12)); "GridSize", typeof(int), typeof(VisualScriptPresenter), new PropertyMetadata(24));
public int GridSize public int GridSize
{ {
@ -138,6 +141,15 @@ namespace Artemis.VisualScripting.Editor.Controls
set => SetValue(SurfaceSizeProperty, value); set => SetValue(SurfaceSizeProperty, value);
} }
public static readonly DependencyProperty AutoFitScriptProperty = DependencyProperty.Register(
"AutoFitScript", typeof(bool), typeof(VisualScriptPresenter), new PropertyMetadata(false, AutoFitScriptChanged));
public bool AutoFitScript
{
get => (bool)GetValue(AutoFitScriptProperty);
set => SetValue(AutoFitScriptProperty, value);
}
#endregion #endregion
#region Constructors #region Constructors
@ -163,6 +175,7 @@ namespace Artemis.VisualScripting.Editor.Controls
_canvas.AllowDrop = true; _canvas.AllowDrop = true;
_canvas.LayoutTransform = _canvasViewPortScale = new ScaleTransform(MaxScale, MaxScale);
_canvas.RenderTransform = _canvasViewPortTransform = new TranslateTransform(0, 0); _canvas.RenderTransform = _canvasViewPortTransform = new TranslateTransform(0, 0);
_canvas.MouseLeftButtonDown += OnCanvasMouseLeftButtonDown; _canvas.MouseLeftButtonDown += OnCanvasMouseLeftButtonDown;
_canvas.MouseLeftButtonUp += OnCanvasMouseLeftButtonUp; _canvas.MouseLeftButtonUp += OnCanvasMouseLeftButtonUp;
@ -177,11 +190,22 @@ namespace Artemis.VisualScripting.Editor.Controls
_cableList.ItemsSource = VisualScript?.Cables; _cableList.ItemsSource = VisualScript?.Cables;
} }
private static void AutoFitScriptChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
if (d is not VisualScriptPresenter scriptPresenter) return;
if ((args.NewValue as bool?) == true)
scriptPresenter.FitScript();
}
private void OnSizeChanged(object sender, SizeChangedEventArgs args) private void OnSizeChanged(object sender, SizeChangedEventArgs args)
{ {
if (sender is not VisualScriptPresenter scriptPresenter) return; if (sender is not VisualScriptPresenter scriptPresenter) return;
scriptPresenter.UpdatePanning(); if (AutoFitScript)
scriptPresenter.FitScript();
else
scriptPresenter.UpdatePanning();
} }
private static void ScriptChanged(DependencyObject d, DependencyPropertyChangedEventArgs args) private static void ScriptChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
@ -194,28 +218,33 @@ namespace Artemis.VisualScripting.Editor.Controls
private void ScriptChanged(VisualScript newScript) private void ScriptChanged(VisualScript newScript)
{ {
if (VisualScript != null) if (VisualScript != null)
{
VisualScript.PropertyChanged -= OnVisualScriptPropertyChanged; VisualScript.PropertyChanged -= OnVisualScriptPropertyChanged;
VisualScript.NodeMoved -= OnVisualScriptNodeMoved;
VisualScript.NodeCollectionChanged -= OnVisualScriptNodeCollectionChanged;
}
VisualScript = newScript; VisualScript = newScript;
if (VisualScript != null) if (VisualScript != null)
{ {
VisualScript.PropertyChanged += OnVisualScriptPropertyChanged; VisualScript.PropertyChanged += OnVisualScriptPropertyChanged;
VisualScript.NodeMoved += OnVisualScriptNodeMoved;
VisualScript.NodeCollectionChanged += OnVisualScriptNodeCollectionChanged;
if (_nodeList != null) if (_nodeList != null)
_nodeList.ItemsSource = VisualScript?.Nodes; _nodeList.ItemsSource = VisualScript?.Nodes;
if (_cableList != null) if (_cableList != null)
_cableList.ItemsSource = VisualScript?.Cables; _cableList.ItemsSource = VisualScript?.Cables;
VisualScript.Nodes.Clear();
foreach (INode node in VisualScript.Script.Nodes)
InitializeNode(node);
} }
VisualScript?.RecreateCables(); VisualScript?.RecreateCables();
CenterAt(new Vector(0, 0)); if (AutoFitScript)
FitScript();
else
CenterAt(new Vector(0, 0));
} }
private void OnVisualScriptPropertyChanged(object sender, PropertyChangedEventArgs args) private void OnVisualScriptPropertyChanged(object sender, PropertyChangedEventArgs args)
@ -225,6 +254,18 @@ namespace Artemis.VisualScripting.Editor.Controls
_cableList.ItemsSource = VisualScript.Cables; _cableList.ItemsSource = VisualScript.Cables;
} }
private void OnVisualScriptNodeMoved(object sender, EventArgs args)
{
if (AutoFitScript)
FitScript();
}
private void OnVisualScriptNodeCollectionChanged(object? sender, EventArgs e)
{
if (AutoFitScript)
FitScript();
}
private void OnCanvasPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs args) private void OnCanvasPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs args)
{ {
_lastRightClickLocation = args.GetPosition(_canvas); _lastRightClickLocation = args.GetPosition(_canvas);
@ -261,6 +302,12 @@ namespace Artemis.VisualScripting.Editor.Controls
private void OnCanvasMouseRightButtonDown(object sender, MouseButtonEventArgs args) private void OnCanvasMouseRightButtonDown(object sender, MouseButtonEventArgs args)
{ {
if (AutoFitScript)
{
args.Handled = true;
return;
}
_dragCanvas = true; _dragCanvas = true;
_dragCanvasStartLocation = args.GetPosition(this); _dragCanvasStartLocation = args.GetPosition(this);
_dragCanvasStartOffset = _viewportCenter; _dragCanvasStartOffset = _viewportCenter;
@ -289,7 +336,7 @@ namespace Artemis.VisualScripting.Editor.Controls
{ {
if (args.RightButton == MouseButtonState.Pressed) if (args.RightButton == MouseButtonState.Pressed)
{ {
Vector newLocation = _dragCanvasStartOffset + (((args.GetPosition(this) - _dragCanvasStartLocation)) * (1.0 / Scale)); Vector newLocation = _dragCanvasStartOffset - (((args.GetPosition(this) - _dragCanvasStartLocation)) * (1.0 / Scale));
CenterAt(newLocation); CenterAt(newLocation);
_movedDuringDrag = true; _movedDuringDrag = true;
@ -327,14 +374,18 @@ namespace Artemis.VisualScripting.Editor.Controls
private void OnCanvasDragOver(object sender, DragEventArgs args) private void OnCanvasDragOver(object sender, DragEventArgs args)
{ {
if (VisualScript == null) return;
if (VisualScript.IsConnecting) if (VisualScript.IsConnecting)
VisualScript.OnDragOver(args.GetPosition(_canvas)); VisualScript.OnDragOver(args.GetPosition(_canvas));
} }
private void OnCanvasMouseWheel(object sender, MouseWheelEventArgs args) private void OnCanvasMouseWheel(object sender, MouseWheelEventArgs args)
{ {
if (AutoFitScript)
{
args.Handled = true;
return;
}
if (args.Delta < 0) if (args.Delta < 0)
Scale /= ScaleFactor; Scale /= ScaleFactor;
else else
@ -342,8 +393,6 @@ namespace Artemis.VisualScripting.Editor.Controls
Scale = Clamp(Scale, MinScale, MaxScale); Scale = Clamp(Scale, MinScale, MaxScale);
_canvas.LayoutTransform = new ScaleTransform(Scale, Scale);
UpdatePanning(); UpdatePanning();
args.Handled = true; args.Handled = true;
@ -373,9 +422,63 @@ namespace Artemis.VisualScripting.Editor.Controls
if (d is not VisualScriptPresenter presenter) return; if (d is not VisualScriptPresenter presenter) return;
if (presenter.VisualScript == null) return; if (presenter.VisualScript == null) return;
presenter._canvasViewPortScale.ScaleX = presenter.Scale;
presenter._canvasViewPortScale.ScaleY = presenter.Scale;
presenter.VisualScript.NodeDragScale = 1.0 / presenter.Scale; presenter.VisualScript.NodeDragScale = 1.0 / presenter.Scale;
} }
private void FitScript()
{
if (_fitPending) return;
_fitPending = true;
Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(FitScriptAction));
}
private void FitScriptAction()
{
_fitPending = false;
if ((Script == null) || (_nodeList == null)) return;
double minX = double.MaxValue;
double maxX = double.MinValue;
double minY = double.MaxValue;
double maxY = double.MinValue;
for (int i = 0; i < _nodeList.Items.Count; i++)
{
DependencyObject container = _nodeList.ItemContainerGenerator.ContainerFromIndex(i);
VisualScriptNodePresenter nodePresenter = GetChildOfType<VisualScriptNodePresenter>(container);
if (nodePresenter != null)
{
minX = Math.Min(minX, nodePresenter.Node.Node.X);
minY = Math.Min(minY, nodePresenter.Node.Node.Y);
maxX = Math.Max(maxX, nodePresenter.Node.Node.X + nodePresenter.ActualWidth);
maxY = Math.Max(maxY, nodePresenter.Node.Node.Y + nodePresenter.ActualHeight);
}
}
if (minX >= (double.MaxValue - 1))
{
Scale = MaxScale;
CenterAt(new Vector(0, 0));
}
else
{
double width = maxX - minX;
double height = maxY - minY;
double scaleX = ActualWidth / width;
double scaleY = ActualHeight / height;
Scale = Clamp(Math.Min(scaleX, scaleY) / 1.05, 0, MaxScale); //DarthAffe 21.08.2021: 5% Border
CenterAt(new Vector(minX + (width / 2.0), minY + (height / 2.0)));
}
}
private void CreateNode(NodeData nodeData) private void CreateNode(NodeData nodeData)
{ {
if (nodeData == null) return; if (nodeData == null) return;
@ -385,21 +488,10 @@ namespace Artemis.VisualScripting.Editor.Controls
INode node = nodeData.CreateNode(Script, null); INode node = nodeData.CreateNode(Script, null);
node.Initialize(Script); node.Initialize(Script);
node.X = _lastRightClickLocation.X - VisualScript.LocationOffset;
node.Y = _lastRightClickLocation.Y - VisualScript.LocationOffset;
Script.AddNode(node); Script.AddNode(node);
InitializeNode(node, _lastRightClickLocation);
}
private void InitializeNode(INode node, Point? initialLocation = null)
{
VisualScriptNode visualScriptNode = new(VisualScript, node);
if (initialLocation != null)
{
visualScriptNode.X = initialLocation.Value.X;
visualScriptNode.Y = initialLocation.Value.Y;
}
visualScriptNode.SnapNodeToGrid();
VisualScript.Nodes.Add(visualScriptNode);
} }
private void CenterAt(Vector vector) private void CenterAt(Vector vector)
@ -414,8 +506,8 @@ namespace Artemis.VisualScripting.Editor.Controls
if (_canvasViewPortTransform == null) return; if (_canvasViewPortTransform == null) return;
double surfaceOffset = (SurfaceSize / 2.0) * Scale; double surfaceOffset = (SurfaceSize / 2.0) * Scale;
_canvasViewPortTransform.X = (((_viewportCenter.X * Scale) + (ActualWidth / 2.0))) - surfaceOffset; _canvasViewPortTransform.X = (((-_viewportCenter.X * Scale) + (ActualWidth / 2.0))) - surfaceOffset;
_canvasViewPortTransform.Y = (((_viewportCenter.Y * Scale) + (ActualHeight / 2.0))) - surfaceOffset; _canvasViewPortTransform.Y = (((-_viewportCenter.Y * Scale) + (ActualHeight / 2.0))) - surfaceOffset;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -436,6 +528,21 @@ namespace Artemis.VisualScripting.Editor.Controls
return new Vector(x, y); return new Vector(x, y);
} }
public static T GetChildOfType<T>(DependencyObject obj)
where T : DependencyObject
{
if (obj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
T result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
#endregion #endregion
} }
} }

View File

@ -2,8 +2,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Threading;
using Artemis.Core; using Artemis.Core;
using Artemis.VisualScripting.Events; using Artemis.VisualScripting.Events;
using Artemis.VisualScripting.ViewModel; using Artemis.VisualScripting.ViewModel;
@ -14,6 +16,8 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
{ {
#region Properties & Fields #region Properties & Fields
private bool _cableRecreationPending = false;
private readonly HashSet<VisualScriptNode> _selectedNodes = new(); private readonly HashSet<VisualScriptNode> _selectedNodes = new();
private readonly Dictionary<VisualScriptNode, (double X, double Y)> _nodeStartPositions = new(); private readonly Dictionary<VisualScriptNode, (double X, double Y)> _nodeStartPositions = new();
private double _nodeDragAccumulationX; private double _nodeDragAccumulationX;
@ -31,6 +35,8 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
set => SetProperty(ref _nodeDragScale, value); set => SetProperty(ref _nodeDragScale, value);
} }
internal double LocationOffset { get; }
private VisualScriptPin _isConnectingPin; private VisualScriptPin _isConnectingPin;
private VisualScriptPin IsConnectingPin private VisualScriptPin IsConnectingPin
{ {
@ -42,6 +48,7 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
} }
} }
private readonly Dictionary<INode, VisualScriptNode> _nodeMapping = new();
public ObservableCollection<VisualScriptNode> Nodes { get; } = new(); public ObservableCollection<VisualScriptNode> Nodes { get; } = new();
public IEnumerable<VisualScriptCable> Cables => Nodes.SelectMany(n => n.InputPins.SelectMany(p => p.InternalConnections)) public IEnumerable<VisualScriptCable> Cables => Nodes.SelectMany(n => n.InputPins.SelectMany(p => p.InternalConnections))
@ -54,6 +61,13 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
#endregion #endregion
#region Events
public event EventHandler NodeMoved;
public event EventHandler NodeCollectionChanged;
#endregion
#region Constructors #region Constructors
public VisualScript(INodeScript script, int surfaceSize, int gridSize) public VisualScript(INodeScript script, int surfaceSize, int gridSize)
@ -62,7 +76,15 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
this.SurfaceSize = surfaceSize; this.SurfaceSize = surfaceSize;
this.GridSize = gridSize; this.GridSize = gridSize;
LocationOffset = SurfaceSize / 2.0;
Nodes.CollectionChanged += OnNodeCollectionChanged; Nodes.CollectionChanged += OnNodeCollectionChanged;
script.NodeAdded += OnScriptNodeAdded;
script.NodeRemoved += OnScriptRemovedAdded;
foreach (INode node in Script.Nodes)
InitializeNode(node);
} }
#endregion #endregion
@ -85,31 +107,69 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
RegisterNodes(args.NewItems.Cast<VisualScriptNode>()); RegisterNodes(args.NewItems.Cast<VisualScriptNode>());
} }
private void OnScriptNodeAdded(object sender, INode node)
{
if (_nodeMapping.ContainsKey(node)) return;
InitializeNode(node);
}
private void OnScriptRemovedAdded(object sender, INode node)
{
if (!_nodeMapping.TryGetValue(node, out VisualScriptNode visualScriptNode)) return;
Nodes.Remove(visualScriptNode);
}
private void InitializeNode(INode node)
{
VisualScriptNode visualScriptNode = new(this, node);
visualScriptNode.SnapNodeToGrid();
Nodes.Add(visualScriptNode);
}
private void RegisterNodes(IEnumerable<VisualScriptNode> nodes) private void RegisterNodes(IEnumerable<VisualScriptNode> nodes)
{ {
foreach (VisualScriptNode node in nodes) foreach (VisualScriptNode node in nodes)
{ {
_nodeMapping.Add(node.Node, node);
node.IsSelectedChanged += OnNodeIsSelectedChanged; node.IsSelectedChanged += OnNodeIsSelectedChanged;
node.DragStarting += OnNodeDragStarting; node.DragStarting += OnNodeDragStarting;
node.DragEnding += OnNodeDragEnding; node.DragEnding += OnNodeDragEnding;
node.DragMoving += OnNodeDragMoving; node.DragMoving += OnNodeDragMoving;
node.PropertyChanged += OnNodePropertyChanged;
if (node.IsSelected) if (node.IsSelected)
_selectedNodes.Add(node); _selectedNodes.Add(node);
} }
NodeCollectionChanged?.Invoke(this, EventArgs.Empty);
} }
private void UnregisterNodes(IEnumerable<VisualScriptNode> nodes) private void UnregisterNodes(IEnumerable<VisualScriptNode> nodes)
{ {
foreach (VisualScriptNode node in nodes) foreach (VisualScriptNode node in nodes)
{ {
_nodeMapping.Remove(node.Node);
node.IsSelectedChanged -= OnNodeIsSelectedChanged; node.IsSelectedChanged -= OnNodeIsSelectedChanged;
node.DragStarting -= OnNodeDragStarting; node.DragStarting -= OnNodeDragStarting;
node.DragEnding -= OnNodeDragEnding; node.DragEnding -= OnNodeDragEnding;
node.DragMoving -= OnNodeDragMoving; node.DragMoving -= OnNodeDragMoving;
node.PropertyChanged -= OnNodePropertyChanged;
_selectedNodes.Remove(node); _selectedNodes.Remove(node);
} }
NodeCollectionChanged?.Invoke(this, EventArgs.Empty);
}
private void OnNodePropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (string.Equals(args.PropertyName, nameof(VisualScriptNode.X), StringComparison.OrdinalIgnoreCase)
|| string.Equals(args.PropertyName, nameof(VisualScriptNode.Y), StringComparison.OrdinalIgnoreCase))
NodeMoved?.Invoke(this, EventArgs.Empty);
} }
private void OnNodeIsSelectedChanged(object sender, VisualScriptNodeIsSelectedChangedEventArgs args) private void OnNodeIsSelectedChanged(object sender, VisualScriptNodeIsSelectedChangedEventArgs args)
@ -184,8 +244,18 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
Script.RemoveNode(node.Node); Script.RemoveNode(node.Node);
} }
internal void RequestCableRecreation()
{
if (_cableRecreationPending) return;
_cableRecreationPending = true;
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(RecreateCables));
}
internal void RecreateCables() internal void RecreateCables()
{ {
_cableRecreationPending = false;
Dictionary<IPin, VisualScriptPin> pinMapping = Nodes.SelectMany(n => n.InputPins) Dictionary<IPin, VisualScriptPin> pinMapping = Nodes.SelectMany(n => n.InputPins)
.Concat(Nodes.SelectMany(n => n.OutputPins)) .Concat(Nodes.SelectMany(n => n.OutputPins))
.Concat(Nodes.SelectMany(n => n.InputPinCollections.SelectMany(p => p.Pins))) .Concat(Nodes.SelectMany(n => n.InputPinCollections.SelectMany(p => p.Pins)))
@ -200,13 +270,13 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
{ {
foreach (IPin connectedPin in pin.ConnectedTo) foreach (IPin connectedPin in pin.ConnectedTo)
if (!connectedPins.Contains(connectedPin)) if (!connectedPins.Contains(connectedPin))
{ if (pinMapping.TryGetValue(pin, out VisualScriptPin pin1)
VisualScriptPin pin1 = pinMapping[pin]; && pinMapping.TryGetValue(connectedPin, out VisualScriptPin pin2))
VisualScriptPin pin2 = pinMapping[connectedPin]; {
VisualScriptCable cable = new(pin1, pin2, false); VisualScriptCable cable = new(pin1, pin2, false);
pin1.InternalConnections.Add(cable); pin1.InternalConnections.Add(cable);
pin2.InternalConnections.Add(cable); pin2.InternalConnections.Add(cable);
} }
connectedPins.Add(pin); connectedPins.Add(pin);
} }

View File

@ -13,7 +13,7 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
{ {
#region Properties & Fields #region Properties & Fields
private double _locationOffset; private readonly Dictionary<IPin, VisualScriptPin> _pinMapping = new();
public VisualScript Script { get; } public VisualScript Script { get; }
public INode Node { get; } public INode Node { get; }
@ -55,20 +55,20 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
public double X public double X
{ {
get => Node.X + _locationOffset; get => Node.X + Script.LocationOffset;
set set
{ {
Node.X = value - _locationOffset; Node.X = value - Script.LocationOffset;
OnPropertyChanged(); OnPropertyChanged();
} }
} }
public double Y public double Y
{ {
get => Node.Y + _locationOffset; get => Node.Y + Script.LocationOffset;
set set
{ {
Node.Y = value - _locationOffset; Node.Y = value - Script.LocationOffset;
OnPropertyChanged(); OnPropertyChanged();
} }
} }
@ -100,8 +100,6 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
Node.PropertyChanged += OnNodePropertyChanged; Node.PropertyChanged += OnNodePropertyChanged;
_locationOffset = script.SurfaceSize / 2.0;
ValidatePins(); ValidatePins();
} }
@ -114,6 +112,12 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
if (string.Equals(args.PropertyName, nameof(Node.Pins), StringComparison.OrdinalIgnoreCase) if (string.Equals(args.PropertyName, nameof(Node.Pins), StringComparison.OrdinalIgnoreCase)
|| string.Equals(args.PropertyName, nameof(Node.PinCollections), StringComparison.OrdinalIgnoreCase)) || string.Equals(args.PropertyName, nameof(Node.PinCollections), StringComparison.OrdinalIgnoreCase))
ValidatePins(); ValidatePins();
else if (string.Equals(args.PropertyName, nameof(Node.X), StringComparison.OrdinalIgnoreCase))
OnPropertyChanged(nameof(X));
else if (string.Equals(args.PropertyName, nameof(Node.Y), StringComparison.OrdinalIgnoreCase))
OnPropertyChanged(nameof(Y));
} }
private void ValidatePins() private void ValidatePins()
@ -199,8 +203,30 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
} }
#endregion #endregion
#region Pin Mapping
_pinMapping.Clear();
foreach (VisualScriptPin pin in InputPins)
_pinMapping.Add(pin.Pin, pin);
foreach (VisualScriptPin pin in OutputPins)
_pinMapping.Add(pin.Pin, pin);
foreach (VisualScriptPinCollection pinCollection in InputPinCollections)
foreach (VisualScriptPin pin in pinCollection.Pins)
_pinMapping.Add(pin.Pin, pin);
foreach (VisualScriptPinCollection pinCollection in OutputPinCollections)
foreach (VisualScriptPin pin in pinCollection.Pins)
_pinMapping.Add(pin.Pin, pin);
#endregion
} }
internal VisualScriptPin GetVisualPin(IPin pin) => ((pin != null) && _pinMapping.TryGetValue(pin, out VisualScriptPin visualScriptPin)) ? visualScriptPin : null;
public void SnapNodeToGrid() public void SnapNodeToGrid()
{ {
X -= X % Script.GridSize; X -= X % Script.GridSize;
@ -219,8 +245,8 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
OnIsSelectedChanged(IsSelected, alterSelection); OnIsSelectedChanged(IsSelected, alterSelection);
} }
public void DragStart() => DragStarting?.Invoke(this, new EventArgs()); public void DragStart() => DragStarting?.Invoke(this, EventArgs.Empty);
public void DragEnd() => DragEnding?.Invoke(this, new EventArgs()); public void DragEnd() => DragEnding?.Invoke(this, EventArgs.Empty);
public void DragMove(double dx, double dy) => DragMoving?.Invoke(this, new VisualScriptNodeDragMovingEventArgs(dx, dy)); public void DragMove(double dx, double dy) => DragMoving?.Invoke(this, new VisualScriptNodeDragMovingEventArgs(dx, dy));
private void OnIsSelectedChanged(bool isSelected, bool alterSelection) => IsSelectedChanged?.Invoke(this, new VisualScriptNodeIsSelectedChangedEventArgs(isSelected, alterSelection)); private void OnIsSelectedChanged(bool isSelected, bool alterSelection) => IsSelectedChanged?.Invoke(this, new VisualScriptNodeIsSelectedChangedEventArgs(isSelected, alterSelection));

View File

@ -19,6 +19,8 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
#region Properties & Fields #region Properties & Fields
private bool _isConnectionUpdated = false;
private VisualScriptPin _isConnectingPin; private VisualScriptPin _isConnectingPin;
private VisualScriptCable _isConnectingCable; private VisualScriptCable _isConnectingCable;
@ -39,8 +41,7 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
} }
} }
public Point AbsoluteCableTargetPosition => Pin.Direction == PinDirection.Input ? new Point(AbsolutePosition.X - CABLE_OFFSET, AbsolutePosition.Y) public Point AbsoluteCableTargetPosition => Pin.Direction == PinDirection.Input ? new Point(AbsolutePosition.X - CABLE_OFFSET, AbsolutePosition.Y) : new Point(AbsolutePosition.X + CABLE_OFFSET, AbsolutePosition.Y);
: new Point(AbsolutePosition.X + CABLE_OFFSET, AbsolutePosition.Y);
#endregion #endregion
@ -50,12 +51,22 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
{ {
this.Node = node; this.Node = node;
this.Pin = pin; this.Pin = pin;
pin.PinConnected += PinConnectionChanged;
pin.PinDisconnected += PinConnectionChanged;
} }
#endregion #endregion
#region Methods #region Methods
private void PinConnectionChanged(object sender, IPin pin)
{
if (_isConnectionUpdated) return;
Node?.Script?.RequestCableRecreation();
}
public void SetConnecting(bool isConnecting) public void SetConnecting(bool isConnecting)
{ {
if (isConnecting) if (isConnecting)
@ -63,8 +74,7 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
if (_isConnectingCable != null) if (_isConnectingCable != null)
SetConnecting(false); SetConnecting(false);
_isConnectingPin = new VisualScriptPin(null, new IsConnectingPin(Pin.Direction == PinDirection.Input ? PinDirection.Output : PinDirection.Input, Pin.Type)) _isConnectingPin = new VisualScriptPin(null, new IsConnectingPin(Pin.Direction == PinDirection.Input ? PinDirection.Output : PinDirection.Input, Pin.Type)) { AbsolutePosition = AbsolutePosition };
{ AbsolutePosition = AbsolutePosition };
_isConnectingCable = new VisualScriptCable(this, _isConnectingPin); _isConnectingCable = new VisualScriptCable(this, _isConnectingPin);
Node.OnIsConnectingPinChanged(_isConnectingPin); Node.OnIsConnectingPinChanged(_isConnectingPin);
} }
@ -81,6 +91,8 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
{ {
if (InternalConnections.Contains(cable)) return; if (InternalConnections.Contains(cable)) return;
_isConnectionUpdated = true;
if (Pin.Direction == PinDirection.Input) if (Pin.Direction == PinDirection.Input)
{ {
List<VisualScriptCable> cables = InternalConnections.ToList(); List<VisualScriptCable> cables = InternalConnections.ToList();
@ -92,21 +104,31 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
Pin.ConnectTo(cable.GetConnectedPin(Pin)); Pin.ConnectTo(cable.GetConnectedPin(Pin));
Node?.OnPinConnected(new PinConnectedEventArgs(this, cable)); Node?.OnPinConnected(new PinConnectedEventArgs(this, cable));
_isConnectionUpdated = false;
} }
public void DisconnectAll() public void DisconnectAll()
{ {
_isConnectionUpdated = true;
List<VisualScriptCable> cables = InternalConnections.ToList(); List<VisualScriptCable> cables = InternalConnections.ToList();
foreach (VisualScriptCable cable in cables) foreach (VisualScriptCable cable in cables)
cable.Disconnect(); cable.Disconnect();
_isConnectionUpdated = false;
} }
internal void Disconnect(VisualScriptCable cable) internal void Disconnect(VisualScriptCable cable)
{ {
_isConnectionUpdated = true;
InternalConnections.Remove(cable); InternalConnections.Remove(cable);
Pin.DisconnectFrom(cable.GetConnectedPin(Pin)); Pin.DisconnectFrom(cable.GetConnectedPin(Pin));
Node?.OnPinDisconnected(new PinDisconnectedEventArgs(this, cable)); Node?.OnPinDisconnected(new PinDisconnectedEventArgs(this, cable));
_isConnectionUpdated = false;
} }
#endregion #endregion

View File

@ -12,6 +12,7 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
public VisualScriptNode Node { get; } public VisualScriptNode Node { get; }
public IPinCollection PinCollection { get; } public IPinCollection PinCollection { get; }
private readonly Dictionary<IPin, VisualScriptPin> _pinMapping = new();
public ObservableCollection<VisualScriptPin> Pins { get; } = new(); public ObservableCollection<VisualScriptPin> Pins { get; } = new();
#endregion #endregion
@ -33,27 +34,47 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
this.Node = node; this.Node = node;
this.PinCollection = pinCollection; this.PinCollection = pinCollection;
pinCollection.PinAdded += OnPinCollectionPinAdded;
pinCollection.PinRemoved += OnPinCollectionPinRemoved;
foreach (IPin pin in PinCollection) foreach (IPin pin in PinCollection)
Pins.Add(new VisualScriptPin(node, pin)); {
VisualScriptPin visualScriptPin = new(node, pin);
_pinMapping.Add(pin, visualScriptPin);
Pins.Add(visualScriptPin);
}
} }
#endregion #endregion
#region Methods #region Methods
private void OnPinCollectionPinRemoved(object sender, IPin pin)
{
if (!_pinMapping.TryGetValue(pin, out VisualScriptPin visualScriptPin)) return;
visualScriptPin.DisconnectAll();
Pins.Remove(visualScriptPin);
}
private void OnPinCollectionPinAdded(object sender, IPin pin)
{
if (_pinMapping.ContainsKey(pin)) return;
VisualScriptPin visualScriptPin = new(Node, pin);
_pinMapping.Add(pin, visualScriptPin);
Pins.Add(visualScriptPin);
}
public void AddPin() public void AddPin()
{ {
IPin pin = PinCollection.AddPin(); PinCollection.AddPin();
Pins.Add(new VisualScriptPin(Node, pin));
} }
public void RemovePin(VisualScriptPin pin) public void RemovePin(VisualScriptPin pin)
{ {
pin.DisconnectAll();
PinCollection.Remove(pin.Pin); PinCollection.Remove(pin.Pin);
Pins.Remove(pin);
} }
public void RemoveAll() public void RemoveAll()
{ {
List<VisualScriptPin> pins = new(Pins); List<VisualScriptPin> pins = new(Pins);