mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
VisualScripting: Fixed Preview; Added auto-fit mode
This commit is contained in:
parent
405d5b756c
commit
7422a9667d
@ -15,7 +15,10 @@ namespace Artemis.Core
|
||||
Type ResultType { get; }
|
||||
|
||||
object? Context { get; set; }
|
||||
|
||||
|
||||
event EventHandler<INode>? NodeAdded;
|
||||
event EventHandler<INode>? NodeRemoved;
|
||||
|
||||
void Run();
|
||||
void AddNode(INode node);
|
||||
void RemoveNode(INode node);
|
||||
|
||||
@ -16,6 +16,9 @@ namespace Artemis.Core
|
||||
|
||||
bool IsEvaluated { get; set; }
|
||||
|
||||
event EventHandler<IPin> PinConnected;
|
||||
event EventHandler<IPin> PinDisconnected;
|
||||
|
||||
void ConnectTo(IPin pin);
|
||||
void DisconnectFrom(IPin pin);
|
||||
}
|
||||
|
||||
@ -9,6 +9,9 @@ namespace Artemis.Core
|
||||
PinDirection Direction { get; }
|
||||
Type Type { get; }
|
||||
|
||||
event EventHandler<IPin> PinAdded;
|
||||
event EventHandler<IPin> PinRemoved;
|
||||
|
||||
IPin AddPin();
|
||||
bool Remove(IPin pin);
|
||||
}
|
||||
|
||||
@ -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<T> : 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
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected CustomViewModelNode()
|
||||
{
|
||||
}
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected CustomViewModelNode(string name, string description) : base(name, description)
|
||||
{
|
||||
}
|
||||
{ }
|
||||
|
||||
public abstract Type CustomViewModelType { get; }
|
||||
public object? BaseCustomViewModel { get; set; }
|
||||
|
||||
@ -28,6 +28,13 @@ namespace Artemis.Core
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
public event EventHandler<INode>? NodeAdded;
|
||||
public event EventHandler<INode>? 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()
|
||||
|
||||
@ -27,6 +27,13 @@ namespace Artemis.Core
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
public event EventHandler<IPin> PinConnected;
|
||||
public event EventHandler<IPin> 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<IPin> connectedPins = new(_connectedTo);
|
||||
|
||||
_connectedTo.Clear();
|
||||
OnPropertyChanged(nameof(ConnectedTo));
|
||||
|
||||
foreach (IPin pin in connectedPins)
|
||||
PinDisconnected?.Invoke(this, pin);
|
||||
}
|
||||
|
||||
private void OnNodeResetting(object sender, EventArgs e)
|
||||
|
||||
@ -20,6 +20,13 @@ namespace Artemis.Core
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
public event EventHandler<IPin> PinAdded;
|
||||
public event EventHandler<IPin> 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();
|
||||
|
||||
|
||||
@ -43,8 +43,9 @@
|
||||
<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">
|
||||
<controls:VisualScriptEditor Script="{Binding RenderProfileElement.DisplayCondition}"
|
||||
Visibility="{Binding RenderProfileElement.DisplayCondition, Converter={StaticResource NullToVisibilityConverter}}" />
|
||||
<controls:VisualScriptPresenter Script="{Binding RenderProfileElement.DisplayCondition}"
|
||||
AutoFitScript="True"
|
||||
Visibility="{Binding RenderProfileElement.DisplayCondition, Converter={StaticResource NullToVisibilityConverter}}" />
|
||||
<Border Opacity="0">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{Binding Color, Source={StaticResource MaterialDesignCardBackground}}" Opacity="0.75" />
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<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)
|
||||
{
|
||||
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<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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<VisualScriptNode> _selectedNodes = new();
|
||||
private readonly Dictionary<VisualScriptNode, (double X, double Y)> _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<INode, VisualScriptNode> _nodeMapping = new();
|
||||
public ObservableCollection<VisualScriptNode> Nodes { get; } = new();
|
||||
|
||||
public IEnumerable<VisualScriptCable> 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<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)
|
||||
{
|
||||
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<VisualScriptNode> 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<IPin, VisualScriptPin> 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);
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private double _locationOffset;
|
||||
private readonly Dictionary<IPin, VisualScriptPin> _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));
|
||||
|
||||
@ -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<VisualScriptCable> 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<VisualScriptCable> 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
|
||||
|
||||
@ -12,6 +12,7 @@ namespace Artemis.VisualScripting.Editor.Controls.Wrapper
|
||||
public VisualScriptNode Node { get; }
|
||||
public IPinCollection PinCollection { get; }
|
||||
|
||||
private readonly Dictionary<IPin, VisualScriptPin> _pinMapping = new();
|
||||
public ObservableCollection<VisualScriptPin> 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<VisualScriptPin> pins = new(Pins);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user