From 7422a9667d6240749474951d97f0f416c9c3bfd3 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sat, 21 Aug 2021 21:48:01 +0200 Subject: [PATCH] VisualScripting: Fixed Preview; Added auto-fit mode --- .../VisualScripting/Interfaces/INodeScript.cs | 5 +- .../VisualScripting/Interfaces/IPin.cs | 3 + .../Interfaces/IPinCollection.cs | 3 + src/Artemis.Core/VisualScripting/Node.cs | 32 ++-- .../VisualScripting/NodeScript.cs | 11 ++ src/Artemis.Core/VisualScripting/Pin.cs | 16 ++ .../VisualScripting/PinCollection.cs | 21 ++- .../DisplayConditionsView.xaml | 5 +- .../Editor/Controls/VisualScriptEditor.cs | 8 - .../Editor/Controls/VisualScriptPresenter.cs | 163 +++++++++++++++--- .../Editor/Controls/Wrapper/VisualScript.cs | 84 ++++++++- .../Controls/Wrapper/VisualScriptNode.cs | 44 ++++- .../Controls/Wrapper/VisualScriptPin.cs | 30 +++- .../Wrapper/VisualScriptPinCollection.cs | 33 +++- 14 files changed, 372 insertions(+), 86 deletions(-) diff --git a/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs b/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs index 76a6df648..dec3cab46 100644 --- a/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs +++ b/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs @@ -15,7 +15,10 @@ namespace Artemis.Core Type ResultType { get; } object? Context { get; set; } - + + event EventHandler? NodeAdded; + event EventHandler? NodeRemoved; + void Run(); void AddNode(INode node); void RemoveNode(INode node); diff --git a/src/Artemis.Core/VisualScripting/Interfaces/IPin.cs b/src/Artemis.Core/VisualScripting/Interfaces/IPin.cs index aef3352cf..0ea3179e4 100644 --- a/src/Artemis.Core/VisualScripting/Interfaces/IPin.cs +++ b/src/Artemis.Core/VisualScripting/Interfaces/IPin.cs @@ -16,6 +16,9 @@ namespace Artemis.Core bool IsEvaluated { get; set; } + event EventHandler PinConnected; + event EventHandler PinDisconnected; + void ConnectTo(IPin pin); void DisconnectFrom(IPin pin); } diff --git a/src/Artemis.Core/VisualScripting/Interfaces/IPinCollection.cs b/src/Artemis.Core/VisualScripting/Interfaces/IPinCollection.cs index 410f269ba..266c31338 100644 --- a/src/Artemis.Core/VisualScripting/Interfaces/IPinCollection.cs +++ b/src/Artemis.Core/VisualScripting/Interfaces/IPinCollection.cs @@ -9,6 +9,9 @@ namespace Artemis.Core PinDirection Direction { get; } Type Type { get; } + event EventHandler PinAdded; + event EventHandler PinRemoved; + IPin AddPin(); bool Remove(IPin pin); } diff --git a/src/Artemis.Core/VisualScripting/Node.cs b/src/Artemis.Core/VisualScripting/Node.cs index 5b504a8ec..ac6b4da8a 100644 --- a/src/Artemis.Core/VisualScripting/Node.cs +++ b/src/Artemis.Core/VisualScripting/Node.cs @@ -1,18 +1,14 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; namespace Artemis.Core { public abstract class Node : CorePropertyChanged, INode { - public event EventHandler Resetting; - #region Properties & Fields private string _name; - public string Name { get => _name; @@ -20,7 +16,6 @@ namespace Artemis.Core } private string _description; - public string Description { get => _description; @@ -28,7 +23,6 @@ namespace Artemis.Core } private double _x; - public double X { get => _x; @@ -36,7 +30,6 @@ namespace Artemis.Core } private double _y; - public double Y { get => _y; @@ -44,7 +37,6 @@ namespace Artemis.Core } private object? _storage; - public object? Storage { get => _storage; @@ -61,11 +53,16 @@ namespace Artemis.Core #endregion + #region Events + + public event EventHandler Resetting; + + #endregion + #region Construtors protected Node() - { - } + { } protected Node(string name, string description) { @@ -138,8 +135,7 @@ namespace Artemis.Core } public virtual void Initialize(INodeScript script) - { - } + { } public abstract void Evaluate(); @@ -154,12 +150,10 @@ namespace Artemis.Core public abstract class Node : CustomViewModelNode where T : ICustomNodeViewModel { protected Node() - { - } + { } protected Node(string name, string description) : base(name, description) - { - } + { } public override Type CustomViewModelType => typeof(T); public T CustomViewModel => (T) BaseCustomViewModel!; @@ -169,13 +163,11 @@ namespace Artemis.Core { /// protected CustomViewModelNode() - { - } + { } /// protected CustomViewModelNode(string name, string description) : base(name, description) - { - } + { } public abstract Type CustomViewModelType { get; } public object? BaseCustomViewModel { get; set; } diff --git a/src/Artemis.Core/VisualScripting/NodeScript.cs b/src/Artemis.Core/VisualScripting/NodeScript.cs index ba35ed922..0094d8daa 100644 --- a/src/Artemis.Core/VisualScripting/NodeScript.cs +++ b/src/Artemis.Core/VisualScripting/NodeScript.cs @@ -28,6 +28,13 @@ namespace Artemis.Core #endregion + #region Events + + public event EventHandler? NodeAdded; + public event EventHandler? NodeRemoved; + + #endregion + #region Constructors public NodeScript(string name, string description, object? context = null) @@ -67,11 +74,15 @@ namespace Artemis.Core public void AddNode(INode node) { _nodes.Add(node); + + NodeAdded?.Invoke(this, node); } public void RemoveNode(INode node) { _nodes.Remove(node); + + NodeRemoved?.Invoke(this, node); } public void Dispose() diff --git a/src/Artemis.Core/VisualScripting/Pin.cs b/src/Artemis.Core/VisualScripting/Pin.cs index c1192b53c..be8f3f63d 100644 --- a/src/Artemis.Core/VisualScripting/Pin.cs +++ b/src/Artemis.Core/VisualScripting/Pin.cs @@ -27,6 +27,13 @@ namespace Artemis.Core #endregion + #region Events + + public event EventHandler PinConnected; + public event EventHandler PinDisconnected; + + #endregion + #region Constructors protected Pin(INode node, string name = "") @@ -46,18 +53,27 @@ namespace Artemis.Core { _connectedTo.Add(pin); OnPropertyChanged(nameof(ConnectedTo)); + + PinConnected?.Invoke(this, pin); } public void DisconnectFrom(IPin pin) { _connectedTo.Remove(pin); OnPropertyChanged(nameof(ConnectedTo)); + + PinDisconnected?.Invoke(this, pin); } public void DisconnectAll() { + List connectedPins = new(_connectedTo); + _connectedTo.Clear(); OnPropertyChanged(nameof(ConnectedTo)); + + foreach (IPin pin in connectedPins) + PinDisconnected?.Invoke(this, pin); } private void OnNodeResetting(object sender, EventArgs e) diff --git a/src/Artemis.Core/VisualScripting/PinCollection.cs b/src/Artemis.Core/VisualScripting/PinCollection.cs index 469abf5dc..4bce68cc1 100644 --- a/src/Artemis.Core/VisualScripting/PinCollection.cs +++ b/src/Artemis.Core/VisualScripting/PinCollection.cs @@ -20,6 +20,13 @@ namespace Artemis.Core #endregion + #region Events + + public event EventHandler PinAdded; + public event EventHandler PinRemoved; + + #endregion + #region Constructors protected PinCollection(INode node, string name, int initialCount) @@ -35,14 +42,26 @@ namespace Artemis.Core #region Methods + public IPin AddPin() { IPin pin = CreatePin(); _pins.Add(pin); + + PinAdded?.Invoke(this, 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(); diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml index 16a6b7000..44bbe92a3 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml @@ -43,8 +43,9 @@ - + diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptEditor.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptEditor.cs index 82977bcf5..8af40eb0b 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptEditor.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptEditor.cs @@ -7,10 +7,6 @@ namespace Artemis.VisualScripting.Editor.Controls { public class VisualScriptEditor : Control { - #region Properties & Fields - - #endregion - #region Dependency Properties public static readonly DependencyProperty ScriptProperty = DependencyProperty.Register( @@ -32,9 +28,5 @@ namespace Artemis.VisualScripting.Editor.Controls } #endregion - - #region Methods - - #endregion } } diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs index bc92fa645..0b939b2a3 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptPresenter.cs @@ -6,6 +6,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; +using System.Windows.Threading; using Artemis.Core; using Artemis.VisualScripting.Editor.Controls.Wrapper; using Artemis.VisualScripting.ViewModel; @@ -32,11 +33,13 @@ namespace Artemis.VisualScripting.Editor.Controls #region Properties & Fields + private bool _fitPending = false; private Canvas _canvas; private ItemsControl _nodeList; private ItemsControl _cableList; private Border _selectionBorder; private TranslateTransform _canvasViewPortTransform; + private ScaleTransform _canvasViewPortScale; private Panel _creationBoxParent; private Vector _viewportCenter = new(0, 0); @@ -121,7 +124,7 @@ namespace Artemis.VisualScripting.Editor.Controls } 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 { @@ -138,6 +141,15 @@ namespace Artemis.VisualScripting.Editor.Controls 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 #region Constructors @@ -163,6 +175,7 @@ namespace Artemis.VisualScripting.Editor.Controls _canvas.AllowDrop = true; + _canvas.LayoutTransform = _canvasViewPortScale = new ScaleTransform(MaxScale, MaxScale); _canvas.RenderTransform = _canvasViewPortTransform = new TranslateTransform(0, 0); _canvas.MouseLeftButtonDown += OnCanvasMouseLeftButtonDown; _canvas.MouseLeftButtonUp += OnCanvasMouseLeftButtonUp; @@ -177,11 +190,22 @@ namespace Artemis.VisualScripting.Editor.Controls _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) { if (sender is not VisualScriptPresenter scriptPresenter) return; - scriptPresenter.UpdatePanning(); + if (AutoFitScript) + scriptPresenter.FitScript(); + else + scriptPresenter.UpdatePanning(); } private static void ScriptChanged(DependencyObject d, DependencyPropertyChangedEventArgs args) @@ -194,28 +218,33 @@ namespace Artemis.VisualScripting.Editor.Controls private void ScriptChanged(VisualScript newScript) { if (VisualScript != null) + { VisualScript.PropertyChanged -= OnVisualScriptPropertyChanged; + VisualScript.NodeMoved -= OnVisualScriptNodeMoved; + VisualScript.NodeCollectionChanged -= OnVisualScriptNodeCollectionChanged; + } VisualScript = newScript; if (VisualScript != null) { VisualScript.PropertyChanged += OnVisualScriptPropertyChanged; + VisualScript.NodeMoved += OnVisualScriptNodeMoved; + VisualScript.NodeCollectionChanged += OnVisualScriptNodeCollectionChanged; if (_nodeList != null) _nodeList.ItemsSource = VisualScript?.Nodes; if (_cableList != null) _cableList.ItemsSource = VisualScript?.Cables; - - VisualScript.Nodes.Clear(); - foreach (INode node in VisualScript.Script.Nodes) - InitializeNode(node); } VisualScript?.RecreateCables(); - CenterAt(new Vector(0, 0)); + if (AutoFitScript) + FitScript(); + else + CenterAt(new Vector(0, 0)); } private void OnVisualScriptPropertyChanged(object sender, PropertyChangedEventArgs args) @@ -225,6 +254,18 @@ namespace Artemis.VisualScripting.Editor.Controls _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) { _lastRightClickLocation = args.GetPosition(_canvas); @@ -261,6 +302,12 @@ namespace Artemis.VisualScripting.Editor.Controls private void OnCanvasMouseRightButtonDown(object sender, MouseButtonEventArgs args) { + if (AutoFitScript) + { + args.Handled = true; + return; + } + _dragCanvas = true; _dragCanvasStartLocation = args.GetPosition(this); _dragCanvasStartOffset = _viewportCenter; @@ -289,7 +336,7 @@ namespace Artemis.VisualScripting.Editor.Controls { 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); _movedDuringDrag = true; @@ -327,14 +374,18 @@ namespace Artemis.VisualScripting.Editor.Controls private void OnCanvasDragOver(object sender, DragEventArgs args) { - if (VisualScript == null) return; - if (VisualScript.IsConnecting) VisualScript.OnDragOver(args.GetPosition(_canvas)); } private void OnCanvasMouseWheel(object sender, MouseWheelEventArgs args) { + if (AutoFitScript) + { + args.Handled = true; + return; + } + if (args.Delta < 0) Scale /= ScaleFactor; else @@ -342,8 +393,6 @@ namespace Artemis.VisualScripting.Editor.Controls Scale = Clamp(Scale, MinScale, MaxScale); - _canvas.LayoutTransform = new ScaleTransform(Scale, Scale); - UpdatePanning(); args.Handled = true; @@ -373,9 +422,63 @@ namespace Artemis.VisualScripting.Editor.Controls if (d is not VisualScriptPresenter presenter) return; if (presenter.VisualScript == null) return; + presenter._canvasViewPortScale.ScaleX = presenter.Scale; + presenter._canvasViewPortScale.ScaleY = 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(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) { if (nodeData == null) return; @@ -385,21 +488,10 @@ namespace Artemis.VisualScripting.Editor.Controls INode node = nodeData.CreateNode(Script, null); node.Initialize(Script); + node.X = _lastRightClickLocation.X - VisualScript.LocationOffset; + node.Y = _lastRightClickLocation.Y - VisualScript.LocationOffset; + 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) @@ -414,8 +506,8 @@ namespace Artemis.VisualScripting.Editor.Controls if (_canvasViewPortTransform == null) return; double surfaceOffset = (SurfaceSize / 2.0) * Scale; - _canvasViewPortTransform.X = (((_viewportCenter.X * Scale) + (ActualWidth / 2.0))) - surfaceOffset; - _canvasViewPortTransform.Y = (((_viewportCenter.Y * Scale) + (ActualHeight / 2.0))) - surfaceOffset; + _canvasViewPortTransform.X = (((-_viewportCenter.X * Scale) + (ActualWidth / 2.0))) - surfaceOffset; + _canvasViewPortTransform.Y = (((-_viewportCenter.Y * Scale) + (ActualHeight / 2.0))) - surfaceOffset; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -436,6 +528,21 @@ namespace Artemis.VisualScripting.Editor.Controls return new Vector(x, y); } + public static T GetChildOfType(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(child); + if (result != null) return result; + } + return null; + } + #endregion } } diff --git a/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScript.cs b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScript.cs index fa5ea0bd9..b63125a2b 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScript.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScript.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; +using System.ComponentModel; using System.Linq; using System.Windows; +using System.Windows.Threading; using Artemis.Core; using Artemis.VisualScripting.Events; using Artemis.VisualScripting.ViewModel; @@ -14,6 +16,8 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper { #region Properties & Fields + private bool _cableRecreationPending = false; + private readonly HashSet _selectedNodes = new(); private readonly Dictionary _nodeStartPositions = new(); private double _nodeDragAccumulationX; @@ -31,6 +35,8 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper set => SetProperty(ref _nodeDragScale, value); } + internal double LocationOffset { get; } + private VisualScriptPin _isConnectingPin; private VisualScriptPin IsConnectingPin { @@ -42,6 +48,7 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper } } + private readonly Dictionary _nodeMapping = new(); public ObservableCollection Nodes { get; } = new(); public IEnumerable Cables => Nodes.SelectMany(n => n.InputPins.SelectMany(p => p.InternalConnections)) @@ -54,6 +61,13 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper #endregion + #region Events + + public event EventHandler NodeMoved; + public event EventHandler NodeCollectionChanged; + + #endregion + #region Constructors public VisualScript(INodeScript script, int surfaceSize, int gridSize) @@ -62,7 +76,15 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper this.SurfaceSize = surfaceSize; this.GridSize = gridSize; + LocationOffset = SurfaceSize / 2.0; + Nodes.CollectionChanged += OnNodeCollectionChanged; + + script.NodeAdded += OnScriptNodeAdded; + script.NodeRemoved += OnScriptRemovedAdded; + + foreach (INode node in Script.Nodes) + InitializeNode(node); } #endregion @@ -85,31 +107,69 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper RegisterNodes(args.NewItems.Cast()); } + 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 nodes) { foreach (VisualScriptNode node in nodes) { + _nodeMapping.Add(node.Node, node); + node.IsSelectedChanged += OnNodeIsSelectedChanged; node.DragStarting += OnNodeDragStarting; node.DragEnding += OnNodeDragEnding; node.DragMoving += OnNodeDragMoving; + node.PropertyChanged += OnNodePropertyChanged; if (node.IsSelected) _selectedNodes.Add(node); } + + NodeCollectionChanged?.Invoke(this, EventArgs.Empty); } private void UnregisterNodes(IEnumerable nodes) { foreach (VisualScriptNode node in nodes) { + _nodeMapping.Remove(node.Node); + node.IsSelectedChanged -= OnNodeIsSelectedChanged; node.DragStarting -= OnNodeDragStarting; node.DragEnding -= OnNodeDragEnding; node.DragMoving -= OnNodeDragMoving; + node.PropertyChanged -= OnNodePropertyChanged; _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) @@ -184,8 +244,18 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper Script.RemoveNode(node.Node); } + internal void RequestCableRecreation() + { + if (_cableRecreationPending) return; + + _cableRecreationPending = true; + Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(RecreateCables)); + } + internal void RecreateCables() { + _cableRecreationPending = false; + Dictionary pinMapping = Nodes.SelectMany(n => n.InputPins) .Concat(Nodes.SelectMany(n => n.OutputPins)) .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) if (!connectedPins.Contains(connectedPin)) - { - VisualScriptPin pin1 = pinMapping[pin]; - VisualScriptPin pin2 = pinMapping[connectedPin]; - VisualScriptCable cable = new(pin1, pin2, false); - pin1.InternalConnections.Add(cable); - pin2.InternalConnections.Add(cable); - } + if (pinMapping.TryGetValue(pin, out VisualScriptPin pin1) + && pinMapping.TryGetValue(connectedPin, out VisualScriptPin pin2)) + { + VisualScriptCable cable = new(pin1, pin2, false); + pin1.InternalConnections.Add(cable); + pin2.InternalConnections.Add(cable); + } connectedPins.Add(pin); } diff --git a/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptNode.cs b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptNode.cs index b69f56a01..778bdc0b9 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptNode.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptNode.cs @@ -13,7 +13,7 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper { #region Properties & Fields - private double _locationOffset; + private readonly Dictionary _pinMapping = new(); public VisualScript Script { get; } public INode Node { get; } @@ -55,20 +55,20 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper public double X { - get => Node.X + _locationOffset; + get => Node.X + Script.LocationOffset; set { - Node.X = value - _locationOffset; + Node.X = value - Script.LocationOffset; OnPropertyChanged(); } } public double Y { - get => Node.Y + _locationOffset; + get => Node.Y + Script.LocationOffset; set { - Node.Y = value - _locationOffset; + Node.Y = value - Script.LocationOffset; OnPropertyChanged(); } } @@ -100,8 +100,6 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper Node.PropertyChanged += OnNodePropertyChanged; - _locationOffset = script.SurfaceSize / 2.0; - ValidatePins(); } @@ -114,6 +112,12 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper if (string.Equals(args.PropertyName, nameof(Node.Pins), StringComparison.OrdinalIgnoreCase) || string.Equals(args.PropertyName, nameof(Node.PinCollections), StringComparison.OrdinalIgnoreCase)) 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() @@ -199,8 +203,30 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper } #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() { X -= X % Script.GridSize; @@ -219,8 +245,8 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper OnIsSelectedChanged(IsSelected, alterSelection); } - public void DragStart() => DragStarting?.Invoke(this, new EventArgs()); - public void DragEnd() => DragEnding?.Invoke(this, new EventArgs()); + public void DragStart() => DragStarting?.Invoke(this, EventArgs.Empty); + public void DragEnd() => DragEnding?.Invoke(this, EventArgs.Empty); 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)); diff --git a/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPin.cs b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPin.cs index 933d8125e..a3720cbf4 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPin.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPin.cs @@ -19,6 +19,8 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper #region Properties & Fields + private bool _isConnectionUpdated = false; + private VisualScriptPin _isConnectingPin; 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) - : 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); #endregion @@ -50,12 +51,22 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper { this.Node = node; this.Pin = pin; + + pin.PinConnected += PinConnectionChanged; + pin.PinDisconnected += PinConnectionChanged; } #endregion #region Methods + private void PinConnectionChanged(object sender, IPin pin) + { + if (_isConnectionUpdated) return; + + Node?.Script?.RequestCableRecreation(); + } + public void SetConnecting(bool isConnecting) { if (isConnecting) @@ -63,8 +74,7 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper if (_isConnectingCable != null) SetConnecting(false); - _isConnectingPin = new VisualScriptPin(null, new IsConnectingPin(Pin.Direction == PinDirection.Input ? PinDirection.Output : PinDirection.Input, Pin.Type)) - { AbsolutePosition = AbsolutePosition }; + _isConnectingPin = new VisualScriptPin(null, new IsConnectingPin(Pin.Direction == PinDirection.Input ? PinDirection.Output : PinDirection.Input, Pin.Type)) { AbsolutePosition = AbsolutePosition }; _isConnectingCable = new VisualScriptCable(this, _isConnectingPin); Node.OnIsConnectingPinChanged(_isConnectingPin); } @@ -81,6 +91,8 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper { if (InternalConnections.Contains(cable)) return; + _isConnectionUpdated = true; + if (Pin.Direction == PinDirection.Input) { List cables = InternalConnections.ToList(); @@ -92,21 +104,31 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper Pin.ConnectTo(cable.GetConnectedPin(Pin)); Node?.OnPinConnected(new PinConnectedEventArgs(this, cable)); + + _isConnectionUpdated = false; } public void DisconnectAll() { + _isConnectionUpdated = true; + List cables = InternalConnections.ToList(); foreach (VisualScriptCable cable in cables) cable.Disconnect(); + + _isConnectionUpdated = false; } internal void Disconnect(VisualScriptCable cable) { + _isConnectionUpdated = true; + InternalConnections.Remove(cable); Pin.DisconnectFrom(cable.GetConnectedPin(Pin)); Node?.OnPinDisconnected(new PinDisconnectedEventArgs(this, cable)); + + _isConnectionUpdated = false; } #endregion diff --git a/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPinCollection.cs b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPinCollection.cs index 761542452..d9a1ac838 100644 --- a/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPinCollection.cs +++ b/src/Artemis.VisualScripting/Editor/Controls/Wrapper/VisualScriptPinCollection.cs @@ -12,6 +12,7 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper public VisualScriptNode Node { get; } public IPinCollection PinCollection { get; } + private readonly Dictionary _pinMapping = new(); public ObservableCollection Pins { get; } = new(); #endregion @@ -33,27 +34,47 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper this.Node = node; this.PinCollection = pinCollection; + pinCollection.PinAdded += OnPinCollectionPinAdded; + pinCollection.PinRemoved += OnPinCollectionPinRemoved; + foreach (IPin pin in PinCollection) - Pins.Add(new VisualScriptPin(node, pin)); + { + VisualScriptPin visualScriptPin = new(node, pin); + _pinMapping.Add(pin, visualScriptPin); + Pins.Add(visualScriptPin); + } } #endregion #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() { - IPin pin = PinCollection.AddPin(); - Pins.Add(new VisualScriptPin(Node, pin)); + PinCollection.AddPin(); } public void RemovePin(VisualScriptPin pin) { - pin.DisconnectAll(); PinCollection.Remove(pin.Pin); - Pins.Remove(pin); } - public void RemoveAll() { List pins = new(Pins);