diff --git a/src/Avalonia/Artemis.UI.Shared/Controls/SelectionRectangle.cs b/src/Avalonia/Artemis.UI.Shared/Controls/SelectionRectangle.cs
index c51659c1b..09e102ae0 100644
--- a/src/Avalonia/Artemis.UI.Shared/Controls/SelectionRectangle.cs
+++ b/src/Avalonia/Artemis.UI.Shared/Controls/SelectionRectangle.cs
@@ -166,23 +166,20 @@ public class SelectionRectangle : Control
((SelectionRectangle) sender).SubscribeToInputElement();
}
-
- private void ParentOnPointerPressed(object? sender, PointerPressedEventArgs e)
+ private void ParentOnPointerMoved(object? sender, PointerEventArgs e)
{
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
return;
- e.Pointer.Capture(this);
-
- _startPosition = e.GetPosition(Parent);
- _absoluteStartPosition = e.GetPosition(VisualRoot);
- _displayRect = null;
- }
-
- private void ParentOnPointerMoved(object? sender, PointerEventArgs e)
- {
+ // Capture the pointer and initialize dragging the first time it moves
if (!ReferenceEquals(e.Pointer.Captured, this))
- return;
+ {
+ e.Pointer.Capture(this);
+
+ _startPosition = e.GetPosition(Parent);
+ _absoluteStartPosition = e.GetPosition(VisualRoot);
+ _displayRect = null;
+ }
Point currentPosition = e.GetPosition(Parent);
Point absoluteCurrentPosition = e.GetPosition(VisualRoot);
@@ -223,7 +220,6 @@ public class SelectionRectangle : Control
{
if (_oldInputElement != null)
{
- _oldInputElement.PointerPressed -= ParentOnPointerPressed;
_oldInputElement.PointerMoved -= ParentOnPointerMoved;
_oldInputElement.PointerReleased -= ParentOnPointerReleased;
}
@@ -232,7 +228,6 @@ public class SelectionRectangle : Control
if (InputElement != null)
{
- InputElement.PointerPressed += ParentOnPointerPressed;
InputElement.PointerMoved += ParentOnPointerMoved;
InputElement.PointerReleased += ParentOnPointerReleased;
}
@@ -259,7 +254,6 @@ public class SelectionRectangle : Control
{
if (_oldInputElement != null)
{
- _oldInputElement.PointerPressed -= ParentOnPointerPressed;
_oldInputElement.PointerMoved -= ParentOnPointerMoved;
_oldInputElement.PointerReleased -= ParentOnPointerReleased;
_oldInputElement = null;
diff --git a/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs
index 652feee62..140914580 100644
--- a/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs
+++ b/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs
@@ -94,7 +94,7 @@ namespace Artemis.UI.Ninject.Factories
{
NodeScriptViewModel NodeScriptViewModel(NodeScript nodeScript);
NodePickerViewModel NodePickerViewModel(NodeScript nodeScript);
- NodeViewModel NodeViewModel(NodeScript nodeScript, INode node);
+ NodeViewModel NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node);
}
public interface INodePinVmFactory
diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml
index fd189eb1d..b08cf9b2c 100644
--- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml
+++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml
@@ -4,6 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:paz="clr-namespace:Avalonia.Controls.PanAndZoom;assembly=Avalonia.Controls.PanAndZoom"
xmlns:visualScripting="clr-namespace:Artemis.UI.Screens.VisualScripting"
+ xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.VisualScripting.NodeScriptView"
x:DataType="visualScripting:NodeScriptViewModel">
@@ -23,7 +24,10 @@
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Background="{DynamicResource LargeCheckerboardBrush}"
- ZoomChanged="ZoomBorder_OnZoomChanged">
+ ZoomChanged="ZoomBorder_OnZoomChanged"
+ MaxZoomX="1"
+ MaxZoomY="1"
+ PointerReleased="ZoomBorder_OnPointerReleased">
@@ -52,7 +56,7 @@
-
+
@@ -65,6 +69,17 @@
+
+
+
+
+
+
diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs
index c3094011b..9ace2bbf8 100644
--- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs
+++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs
@@ -1,6 +1,11 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
+using Artemis.UI.Shared.Controls;
+using Artemis.UI.Shared.Events;
using Avalonia;
using Avalonia.Controls;
+using Avalonia.Controls.Generators;
using Avalonia.Controls.PanAndZoom;
using Avalonia.Input;
using Avalonia.Interactivity;
@@ -15,13 +20,17 @@ namespace Artemis.UI.Screens.VisualScripting
{
private readonly ZoomBorder _zoomBorder;
private readonly Grid _grid;
+ private readonly ItemsControl _nodesContainer;
+ private readonly SelectionRectangle _selectionRectangle;
public NodeScriptView()
{
InitializeComponent();
+ _nodesContainer = this.Find("NodesContainer");
_zoomBorder = this.Find("ZoomBorder");
_grid = this.Find("ContainerGrid");
+ _selectionRectangle = this.Find("SelectionRectangle");
_zoomBorder.PropertyChanged += ZoomBorderOnPropertyChanged;
UpdateZoomBorderBackground();
@@ -56,5 +65,23 @@ namespace Artemis.UI.Screens.VisualScripting
{
UpdateZoomBorderBackground();
}
+
+ private void SelectionRectangle_OnSelectionUpdated(object? sender, SelectionRectangleEventArgs e)
+ {
+ List itemContainerInfos = _nodesContainer.ItemContainerGenerator.Containers.Where(c => c.ContainerControl.Bounds.Intersects(e.Rectangle)).ToList();
+ List nodes = itemContainerInfos.Where(c => c.Item is NodeViewModel).Select(c => (NodeViewModel) c.Item).ToList();
+ ViewModel?.UpdateNodeSelection(nodes, e.KeyModifiers.HasFlag(KeyModifiers.Shift), e.KeyModifiers.HasFlag(KeyModifiers.Control));
+ }
+
+ private void SelectionRectangle_OnSelectionFinished(object? sender, SelectionRectangleEventArgs e)
+ {
+ ViewModel?.FinishNodeSelection();
+ }
+
+ private void ZoomBorder_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
+ {
+ if (!_selectionRectangle.IsSelecting)
+ ViewModel?.ClearNodeSelection();
+ }
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs
index 88caf49a6..26f42fd97 100644
--- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs
@@ -1,10 +1,10 @@
using System;
+using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using Artemis.Core;
using Artemis.Core.Events;
-using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.NodeEditor;
@@ -16,19 +16,16 @@ namespace Artemis.UI.Screens.VisualScripting;
public class NodeScriptViewModel : ActivatableViewModelBase
{
- private readonly INodeService _nodeService;
- private readonly INodeEditorService _nodeEditorService;
private readonly INodeVmFactory _nodeVmFactory;
+ private List? _initialNodeSelection;
- public NodeScriptViewModel(NodeScript nodeScript, INodeVmFactory nodeVmFactory, INodeService nodeService, INodeEditorService nodeEditorService)
+ public NodeScriptViewModel(NodeScript nodeScript, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService)
{
_nodeVmFactory = nodeVmFactory;
- _nodeService = nodeService;
- _nodeEditorService = nodeEditorService;
NodeScript = nodeScript;
NodePickerViewModel = _nodeVmFactory.NodePickerViewModel(nodeScript);
- History = _nodeEditorService.GetHistory(NodeScript);
+ History = nodeEditorService.GetHistory(NodeScript);
this.WhenActivated(d =>
{
@@ -42,7 +39,9 @@ public class NodeScriptViewModel : ActivatableViewModelBase
NodeViewModels = new ObservableCollection();
foreach (INode nodeScriptNode in NodeScript.Nodes)
- NodeViewModels.Add(_nodeVmFactory.NodeViewModel(NodeScript, nodeScriptNode));
+ NodeViewModels.Add(_nodeVmFactory.NodeViewModel(this, nodeScriptNode));
+
+ CableViewModels = new ObservableCollection();
}
public NodeScript NodeScript { get; }
@@ -51,9 +50,60 @@ public class NodeScriptViewModel : ActivatableViewModelBase
public NodePickerViewModel NodePickerViewModel { get; }
public NodeEditorHistory History { get; }
+ public void UpdateNodeSelection(List nodes, bool expand, bool invert)
+ {
+ _initialNodeSelection ??= NodeViewModels.Where(vm => vm.IsSelected).ToList();
+
+ if (expand)
+ {
+ foreach (NodeViewModel nodeViewModel in nodes)
+ nodeViewModel.IsSelected = true;
+ }
+ else if (invert)
+ {
+ foreach (NodeViewModel nodeViewModel in nodes)
+ nodeViewModel.IsSelected = !_initialNodeSelection.Contains(nodeViewModel);
+ }
+ else
+ {
+ foreach (NodeViewModel nodeViewModel in nodes)
+ nodeViewModel.IsSelected = true;
+ foreach (NodeViewModel nodeViewModel in NodeViewModels.Except(nodes))
+ nodeViewModel.IsSelected = false;
+ }
+ }
+
+ public void FinishNodeSelection()
+ {
+ _initialNodeSelection = null;
+ }
+
+ public void ClearNodeSelection()
+ {
+ foreach (NodeViewModel nodeViewModel in NodeViewModels)
+ nodeViewModel.IsSelected = false;
+ }
+
+ public void StartNodeDrag(Point position)
+ {
+ foreach (NodeViewModel nodeViewModel in NodeViewModels)
+ nodeViewModel.SaveDragOffset(position);
+ }
+
+ public void UpdateNodeDrag(Point position)
+ {
+ foreach (NodeViewModel nodeViewModel in NodeViewModels)
+ nodeViewModel.UpdatePosition(position);
+ }
+
+ public void FinishNodeDrag()
+ {
+ // TODO: Command
+ }
+
private void HandleNodeAdded(SingleValueEventArgs eventArgs)
{
- NodeViewModels.Add(_nodeVmFactory.NodeViewModel(NodeScript, eventArgs.Value));
+ NodeViewModels.Add(_nodeVmFactory.NodeViewModel(this, eventArgs.Value));
}
private void HandleNodeRemoved(SingleValueEventArgs eventArgs)
diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml
index a3ce0fbda..33b2bb1b2 100644
--- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml
+++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml
@@ -13,14 +13,29 @@
+
+
+
+
+
+
+
+
+
-
+
-
+
diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml.cs
index 9244d4791..6a3cf384a 100644
--- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml.cs
+++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml.cs
@@ -1,12 +1,18 @@
using System;
+using System.Collections.Generic;
using Avalonia;
+using Avalonia.Controls.PanAndZoom;
+using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
+using Avalonia.VisualTree;
namespace Artemis.UI.Screens.VisualScripting;
public class NodeView : ReactiveUserControl
{
+ private bool _dragging;
+
public NodeView()
{
InitializeComponent();
@@ -33,4 +39,40 @@ public class NodeView : ReactiveUserControl
{
AvaloniaXamlLoader.Load(this);
}
+
+ private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
+ {
+ if (ViewModel == null || e.InitialPressMouseButton != MouseButton.Left)
+ return;
+
+ if (_dragging)
+ {
+ _dragging = false;
+ ViewModel.NodeScriptViewModel.FinishNodeDrag();
+ e.Pointer.Capture(null);
+ return;
+ }
+
+ ViewModel.NodeScriptViewModel.UpdateNodeSelection(new List {ViewModel}, e.KeyModifiers.HasFlag(KeyModifiers.Shift), e.KeyModifiers.HasFlag(KeyModifiers.Control));
+ ViewModel.NodeScriptViewModel.FinishNodeSelection();
+
+ e.Handled = true;
+ }
+
+ private void InputElement_OnPointerMoved(object? sender, PointerEventArgs e)
+ {
+ PointerPoint point = e.GetCurrentPoint(this.FindAncestorOfType());
+ if (ViewModel == null || !point.Properties.IsLeftButtonPressed)
+ return;
+
+ if (!_dragging)
+ {
+ _dragging = true;
+ ViewModel.NodeScriptViewModel.StartNodeDrag(point.Position);
+ e.Pointer.Capture((IInputElement?) sender);
+ }
+ ViewModel.NodeScriptViewModel.UpdateNodeDrag(point.Position);
+
+ e.Handled = true;
+ }
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs
index 07be3a158..13eb8c500 100644
--- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs
@@ -4,10 +4,12 @@ using System.Reactive;
using System.Reactive.Linq;
using Artemis.Core;
using Artemis.UI.Ninject.Factories;
+using Artemis.UI.Screens.SurfaceEditor;
using Artemis.UI.Screens.VisualScripting.Pins;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.NodeEditor;
using Artemis.UI.Shared.Services.NodeEditor.Commands;
+using Avalonia;
using Avalonia.Controls.Mixins;
using DynamicData;
using ReactiveUI;
@@ -16,16 +18,18 @@ namespace Artemis.UI.Screens.VisualScripting;
public class NodeViewModel : ActivatableViewModelBase
{
- private readonly NodeScript _nodeScript;
private readonly INodeEditorService _nodeEditorService;
private ICustomNodeViewModel? _customNodeViewModel;
private ReactiveCommand? _deleteNode;
private ObservableAsPropertyHelper? _isStaticNode;
+ private double _dragOffsetX;
+ private double _dragOffsetY;
+ private bool _isSelected;
- public NodeViewModel(NodeScript nodeScript, INode node, INodePinVmFactory nodePinVmFactory, INodeEditorService nodeEditorService)
+ public NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node, INodePinVmFactory nodePinVmFactory, INodeEditorService nodeEditorService)
{
- _nodeScript = nodeScript;
+ NodeScriptViewModel = nodeScriptViewModel;
_nodeEditorService = nodeEditorService;
Node = node;
@@ -54,6 +58,7 @@ public class NodeViewModel : ActivatableViewModelBase
public bool IsStaticNode => _isStaticNode?.Value ?? true;
+ public NodeScriptViewModel NodeScriptViewModel { get; set; }
public INode Node { get; }
public ReadOnlyObservableCollection InputPinViewModels { get; }
public ReadOnlyObservableCollection InputPinCollectionViewModels { get; }
@@ -72,8 +77,32 @@ public class NodeViewModel : ActivatableViewModelBase
set => RaiseAndSetIfChanged(ref _deleteNode, value);
}
+ public bool IsSelected
+ {
+ get => _isSelected;
+ set => RaiseAndSetIfChanged(ref _isSelected, value);
+ }
+
+ public void SaveDragOffset(Point mouseStartPosition)
+ {
+ if (!IsSelected)
+ return;
+
+ _dragOffsetX = Node.X - mouseStartPosition.X;
+ _dragOffsetY = Node.Y - mouseStartPosition.Y;
+ }
+
+ public void UpdatePosition(Point mousePosition)
+ {
+ if (!IsSelected)
+ return;
+
+ Node.X = Math.Round((mousePosition.X + _dragOffsetX) / 10d, 0, MidpointRounding.AwayFromZero) * 10d;
+ Node.Y = Math.Round((mousePosition.Y + _dragOffsetY) / 10d, 0, MidpointRounding.AwayFromZero) * 10d;
+ }
+
private void ExecuteDeleteNode()
{
- _nodeEditorService.ExecuteCommand(_nodeScript, new DeleteNode(_nodeScript, Node));
+ _nodeEditorService.ExecuteCommand(NodeScriptViewModel.NodeScript, new DeleteNode(NodeScriptViewModel.NodeScript, Node));
}
}
\ No newline at end of file