mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Node editor - Added copy/pasting
This commit is contained in:
parent
2c32abaee5
commit
35b593a31d
@ -4,6 +4,22 @@ namespace Artemis.Storage.Entities.Profile.Nodes;
|
|||||||
|
|
||||||
public class NodeConnectionEntity
|
public class NodeConnectionEntity
|
||||||
{
|
{
|
||||||
|
public NodeConnectionEntity()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodeConnectionEntity(NodeConnectionEntity nodeConnectionEntity)
|
||||||
|
{
|
||||||
|
SourceType = nodeConnectionEntity.SourceType;
|
||||||
|
SourceNode = nodeConnectionEntity.SourceNode;
|
||||||
|
TargetNode = nodeConnectionEntity.TargetNode;
|
||||||
|
SourcePinCollectionId = nodeConnectionEntity.SourcePinCollectionId;
|
||||||
|
SourcePinId = nodeConnectionEntity.SourcePinId;
|
||||||
|
TargetType = nodeConnectionEntity.TargetType;
|
||||||
|
TargetPinCollectionId = nodeConnectionEntity.TargetPinCollectionId;
|
||||||
|
TargetPinId = nodeConnectionEntity.TargetPinId;
|
||||||
|
}
|
||||||
|
|
||||||
public string SourceType { get; set; }
|
public string SourceType { get; set; }
|
||||||
public Guid SourceNode { get; set; }
|
public Guid SourceNode { get; set; }
|
||||||
public Guid TargetNode { get; set; }
|
public Guid TargetNode { get; set; }
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Artemis.Storage.Entities.Profile.Nodes;
|
namespace Artemis.Storage.Entities.Profile.Nodes;
|
||||||
|
|
||||||
@ -10,6 +11,22 @@ public class NodeEntity
|
|||||||
PinCollections = new List<NodePinCollectionEntity>();
|
PinCollections = new List<NodePinCollectionEntity>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public NodeEntity(NodeEntity nodeEntity)
|
||||||
|
{
|
||||||
|
Id = nodeEntity.Id;
|
||||||
|
Type = nodeEntity.Type;
|
||||||
|
PluginId = nodeEntity.PluginId;
|
||||||
|
|
||||||
|
Name = nodeEntity.Name;
|
||||||
|
Description = nodeEntity.Description;
|
||||||
|
IsExitNode = nodeEntity.IsExitNode;
|
||||||
|
X = nodeEntity.X;
|
||||||
|
Y = nodeEntity.Y;
|
||||||
|
Storage = nodeEntity.Storage;
|
||||||
|
|
||||||
|
PinCollections = nodeEntity.PinCollections.Select(p => new NodePinCollectionEntity(p)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public string Type { get; set; }
|
public string Type { get; set; }
|
||||||
public Guid PluginId { get; set; }
|
public Guid PluginId { get; set; }
|
||||||
|
|||||||
@ -2,6 +2,17 @@
|
|||||||
|
|
||||||
public class NodePinCollectionEntity
|
public class NodePinCollectionEntity
|
||||||
{
|
{
|
||||||
|
public NodePinCollectionEntity()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodePinCollectionEntity(NodePinCollectionEntity nodePinCollectionEntity)
|
||||||
|
{
|
||||||
|
Id = nodePinCollectionEntity.Id;
|
||||||
|
Direction = nodePinCollectionEntity.Direction;
|
||||||
|
Amount = nodePinCollectionEntity.Amount;
|
||||||
|
}
|
||||||
|
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public int Direction { set; get; }
|
public int Direction { set; get; }
|
||||||
public int Amount { get; set; }
|
public int Amount { get; set; }
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
|
namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||||
@ -36,7 +37,8 @@ public class AddNode : INodeEditorCommand, IDisposable
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Execute()
|
public void Execute()
|
||||||
{
|
{
|
||||||
_nodeScript.AddNode(_node);
|
if (!_nodeScript.Nodes.Contains(_node))
|
||||||
|
_nodeScript.AddNode(_node);
|
||||||
_isRemoved = false;
|
_isRemoved = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
72
src/Artemis.UI/Models/NodesClipboardModel.cs
Normal file
72
src/Artemis.UI/Models/NodesClipboardModel.cs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.Storage.Entities.Profile.Nodes;
|
||||||
|
using FluentAvalonia.Core;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Models;
|
||||||
|
|
||||||
|
public class NodesClipboardModel
|
||||||
|
{
|
||||||
|
public NodesClipboardModel(NodeScript nodeScript, List<INode> nodes)
|
||||||
|
{
|
||||||
|
nodeScript.Save();
|
||||||
|
|
||||||
|
// Grab all entities belonging to provided nodes
|
||||||
|
Nodes = nodeScript.Entity.Nodes.Where(e => nodes.Any(n => n.Id == e.Id)).ToList();
|
||||||
|
// Grab all connections between provided nodes
|
||||||
|
Connections = nodeScript.Entity.Connections.Where(e => nodes.Any(n => n.Id == e.SourceNode) && nodes.Any(n => n.Id == e.TargetNode)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodesClipboardModel()
|
||||||
|
{
|
||||||
|
Nodes = new List<NodeEntity>();
|
||||||
|
Connections = new List<NodeConnectionEntity>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<NodeEntity> Nodes { get; set; }
|
||||||
|
public List<NodeConnectionEntity> Connections { get; set; }
|
||||||
|
|
||||||
|
public List<INode> Paste(NodeScript nodeScript, double x, double y)
|
||||||
|
{
|
||||||
|
if (!Nodes.Any())
|
||||||
|
return new List<INode>();
|
||||||
|
|
||||||
|
nodeScript.Save();
|
||||||
|
|
||||||
|
// Copy the entities, not messing with the originals
|
||||||
|
List<NodeEntity> nodes = Nodes.Select(n => new NodeEntity(n)).ToList();
|
||||||
|
List<NodeConnectionEntity> connections = Connections.Select(c => new NodeConnectionEntity(c)).ToList();
|
||||||
|
|
||||||
|
double xOffset = x - nodes.Min(n => n.X);
|
||||||
|
double yOffset = y - nodes.Min(n => n.Y);
|
||||||
|
|
||||||
|
foreach (NodeEntity node in nodes)
|
||||||
|
{
|
||||||
|
// Give each node a new GUID, updating any connections to it
|
||||||
|
Guid newGuid = Guid.NewGuid();
|
||||||
|
foreach (NodeConnectionEntity connection in connections)
|
||||||
|
{
|
||||||
|
if (connection.SourceNode == node.Id)
|
||||||
|
connection.SourceNode = newGuid;
|
||||||
|
else if (connection.TargetNode == node.Id)
|
||||||
|
connection.TargetNode = newGuid;
|
||||||
|
|
||||||
|
// Only add the connection if this is the first time we hit it
|
||||||
|
if (!nodeScript.Entity.Connections.Contains(connection))
|
||||||
|
nodeScript.Entity.Connections.Add(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Id = newGuid;
|
||||||
|
node.X += xOffset;
|
||||||
|
node.Y += yOffset;
|
||||||
|
nodeScript.Entity.Nodes.Add(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeScript.Load();
|
||||||
|
|
||||||
|
// Return the newly created nodes
|
||||||
|
return nodeScript.Nodes.Where(n => nodes.Any(e => e.Id == n.Id)).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -24,11 +24,6 @@
|
|||||||
Stroke="{CompiledBinding CableColor, Converter={StaticResource ColorToSolidColorBrushConverter}}"
|
Stroke="{CompiledBinding CableColor, Converter={StaticResource ColorToSolidColorBrushConverter}}"
|
||||||
StrokeThickness="4"
|
StrokeThickness="4"
|
||||||
StrokeLineCap="Round">
|
StrokeLineCap="Round">
|
||||||
<Path.Transitions>
|
|
||||||
<Transitions>
|
|
||||||
<ThicknessTransition Property="Margin" Duration="200"></ThicknessTransition>
|
|
||||||
</Transitions>
|
|
||||||
</Path.Transitions>
|
|
||||||
<Path.Data>
|
<Path.Data>
|
||||||
<PathGeometry>
|
<PathGeometry>
|
||||||
<PathGeometry.Figures>
|
<PathGeometry.Figures>
|
||||||
@ -47,8 +42,6 @@
|
|||||||
BorderThickness="2"
|
BorderThickness="2"
|
||||||
CornerRadius="3"
|
CornerRadius="3"
|
||||||
Padding="4"
|
Padding="4"
|
||||||
Canvas.Left="{CompiledBinding ValuePoint.X}"
|
|
||||||
Canvas.Top="{CompiledBinding ValuePoint.Y}"
|
|
||||||
IsVisible="{CompiledBinding DisplayValue}">
|
IsVisible="{CompiledBinding DisplayValue}">
|
||||||
<ContentControl Content="{CompiledBinding FromViewModel.Pin.PinValue}">
|
<ContentControl Content="{CompiledBinding FromViewModel.Pin.PinValue}">
|
||||||
<ContentControl.DataTemplates>
|
<ContentControl.DataTemplates>
|
||||||
|
|||||||
@ -8,6 +8,7 @@ using Avalonia.Input;
|
|||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
|
using Avalonia.Rendering;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.VisualScripting;
|
namespace Artemis.UI.Screens.VisualScripting;
|
||||||
@ -29,9 +30,9 @@ public class CableView : ReactiveUserControl<CableViewModel>
|
|||||||
{
|
{
|
||||||
_valueBorder.GetObservable(BoundsProperty).Subscribe(rect => _valueBorder.RenderTransform = new TranslateTransform(rect.Width / 2 * -1, rect.Height / 2 * -1)).DisposeWith(d);
|
_valueBorder.GetObservable(BoundsProperty).Subscribe(rect => _valueBorder.RenderTransform = new TranslateTransform(rect.Width / 2 * -1, rect.Height / 2 * -1)).DisposeWith(d);
|
||||||
|
|
||||||
ViewModel.WhenAnyValue(vm => vm.FromPoint).Subscribe(_ => Update()).DisposeWith(d);
|
ViewModel.WhenAnyValue(vm => vm.FromPoint).Subscribe(_ => Update(true)).DisposeWith(d);
|
||||||
ViewModel.WhenAnyValue(vm => vm.ToPoint).Subscribe(_ => Update()).DisposeWith(d);
|
ViewModel.WhenAnyValue(vm => vm.ToPoint).Subscribe(_ => Update(false)).DisposeWith(d);
|
||||||
Update();
|
Update(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,10 +41,12 @@ public class CableView : ReactiveUserControl<CableViewModel>
|
|||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Update()
|
private void Update(bool from)
|
||||||
{
|
{
|
||||||
// Workaround for https://github.com/AvaloniaUI/Avalonia/issues/4748
|
// Workaround for https://github.com/AvaloniaUI/Avalonia/issues/4748
|
||||||
_cablePath.Margin = _cablePath.Margin != new Thickness(0, 0, 0, 0) ? new Thickness(0, 0, 0, 0) : new Thickness(1, 1, 0, 0);
|
_cablePath.Margin = new Thickness(_cablePath.Margin.Left + 1, _cablePath.Margin.Top + 1, 0, 0);
|
||||||
|
if (_cablePath.Margin.Left > 2)
|
||||||
|
_cablePath.Margin = new Thickness(0, 0, 0, 0);
|
||||||
|
|
||||||
PathFigure pathFigure = ((PathGeometry) _cablePath.Data).Figures.First();
|
PathFigure pathFigure = ((PathGeometry) _cablePath.Data).Figures.First();
|
||||||
BezierSegment segment = (BezierSegment) pathFigure.Segments!.First();
|
BezierSegment segment = (BezierSegment) pathFigure.Segments!.First();
|
||||||
@ -51,6 +54,11 @@ public class CableView : ReactiveUserControl<CableViewModel>
|
|||||||
segment.Point1 = new Point(ViewModel.FromPoint.X + CABLE_OFFSET, ViewModel.FromPoint.Y);
|
segment.Point1 = new Point(ViewModel.FromPoint.X + CABLE_OFFSET, ViewModel.FromPoint.Y);
|
||||||
segment.Point2 = new Point(ViewModel.ToPoint.X - CABLE_OFFSET, ViewModel.ToPoint.Y);
|
segment.Point2 = new Point(ViewModel.ToPoint.X - CABLE_OFFSET, ViewModel.ToPoint.Y);
|
||||||
segment.Point3 = new Point(ViewModel.ToPoint.X, ViewModel.ToPoint.Y);
|
segment.Point3 = new Point(ViewModel.ToPoint.X, ViewModel.ToPoint.Y);
|
||||||
|
|
||||||
|
Canvas.SetLeft(_valueBorder, ViewModel.FromPoint.X + (ViewModel.ToPoint.X - ViewModel.FromPoint.X) / 2);
|
||||||
|
Canvas.SetTop(_valueBorder, ViewModel.FromPoint.Y + (ViewModel.ToPoint.Y - ViewModel.FromPoint.Y) / 2);
|
||||||
|
|
||||||
|
_cablePath.InvalidateVisual();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPointerEnter(object? sender, PointerEventArgs e)
|
private void OnPointerEnter(object? sender, PointerEventArgs e)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
@ -27,7 +28,6 @@ public class CableViewModel : ActivatableViewModelBase
|
|||||||
private PinViewModel? _fromViewModel;
|
private PinViewModel? _fromViewModel;
|
||||||
private ObservableAsPropertyHelper<Point>? _toPoint;
|
private ObservableAsPropertyHelper<Point>? _toPoint;
|
||||||
private PinViewModel? _toViewModel;
|
private PinViewModel? _toViewModel;
|
||||||
private ObservableAsPropertyHelper<Point>? _valuePoint;
|
|
||||||
|
|
||||||
public CableViewModel(NodeScriptViewModel nodeScriptViewModel, IPin from, IPin to, ISettingsService settingsService)
|
public CableViewModel(NodeScriptViewModel nodeScriptViewModel, IPin from, IPin to, ISettingsService settingsService)
|
||||||
{
|
{
|
||||||
@ -63,11 +63,6 @@ public class CableViewModel : ActivatableViewModelBase
|
|||||||
.Switch()
|
.Switch()
|
||||||
.ToProperty(this, vm => vm.ToPoint)
|
.ToProperty(this, vm => vm.ToPoint)
|
||||||
.DisposeWith(d);
|
.DisposeWith(d);
|
||||||
_valuePoint = this.WhenAnyValue(vm => vm.FromPoint, vm => vm.ToPoint).Select(tuple => new Point(
|
|
||||||
tuple.Item1.X + (tuple.Item2.X - tuple.Item1.X) / 2,
|
|
||||||
tuple.Item1.Y + (tuple.Item2.Y - tuple.Item1.Y) / 2
|
|
||||||
)).ToProperty(this, vm => vm.ValuePoint)
|
|
||||||
.DisposeWith(d);
|
|
||||||
|
|
||||||
// Not a perfect solution but this makes sure the cable never renders at 0,0 (can happen when the cable spawns before the pin ever rendered)
|
// Not a perfect solution but this makes sure the cable never renders at 0,0 (can happen when the cable spawns before the pin ever rendered)
|
||||||
_connected = this.WhenAnyValue(vm => vm.FromPoint, vm => vm.ToPoint)
|
_connected = this.WhenAnyValue(vm => vm.FromPoint, vm => vm.ToPoint)
|
||||||
@ -104,11 +99,10 @@ public class CableViewModel : ActivatableViewModelBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public bool Connected => _connected?.Value ?? false;
|
public bool Connected => _connected?.Value ?? false;
|
||||||
public bool IsFirst => _from.ConnectedTo[0] == _to;
|
public bool IsFirst => _from.ConnectedTo.FirstOrDefault() == _to;
|
||||||
|
|
||||||
public Point FromPoint => _fromPoint?.Value ?? new Point();
|
public Point FromPoint => _fromPoint?.Value ?? new Point();
|
||||||
public Point ToPoint => _toPoint?.Value ?? new Point();
|
public Point ToPoint => _toPoint?.Value ?? new Point();
|
||||||
public Point ValuePoint => _valuePoint?.Value ?? new Point();
|
|
||||||
public Color CableColor => _cableColor?.Value ?? new Color(255, 255, 255, 255);
|
public Color CableColor => _cableColor?.Value ?? new Color(255, 255, 255, 255);
|
||||||
|
|
||||||
public void UpdateDisplayValue(bool hoveringOver)
|
public void UpdateDisplayValue(bool hoveringOver)
|
||||||
|
|||||||
@ -30,6 +30,8 @@
|
|||||||
<KeyBinding Command="{CompiledBinding ClearSelection}" Gesture="Escape" />
|
<KeyBinding Command="{CompiledBinding ClearSelection}" Gesture="Escape" />
|
||||||
<KeyBinding Command="{CompiledBinding DeleteSelected}" Gesture="Delete" />
|
<KeyBinding Command="{CompiledBinding DeleteSelected}" Gesture="Delete" />
|
||||||
<KeyBinding Command="{CompiledBinding DuplicateSelected}" Gesture="Ctrl+D" />
|
<KeyBinding Command="{CompiledBinding DuplicateSelected}" Gesture="Ctrl+D" />
|
||||||
|
<KeyBinding Command="{CompiledBinding CopySelected}" Gesture="Ctrl+C" />
|
||||||
|
<KeyBinding Command="{CompiledBinding PasteSelected}" Gesture="Ctrl+V" />
|
||||||
<KeyBinding Command="{CompiledBinding History.Undo}" Gesture="Ctrl+Z" />
|
<KeyBinding Command="{CompiledBinding History.Undo}" Gesture="Ctrl+Z" />
|
||||||
<KeyBinding Command="{CompiledBinding History.Redo}" Gesture="Ctrl+Y" />
|
<KeyBinding Command="{CompiledBinding History.Redo}" Gesture="Ctrl+Y" />
|
||||||
</UserControl.KeyBindings>
|
</UserControl.KeyBindings>
|
||||||
|
|||||||
@ -14,6 +14,7 @@ using Avalonia.Markup.Xaml;
|
|||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
using DynamicData.Binding;
|
using DynamicData.Binding;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
@ -39,6 +40,8 @@ public class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
|
|||||||
|
|
||||||
_zoomBorder.AddHandler(PointerReleasedEvent, CanvasOnPointerReleased, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
|
_zoomBorder.AddHandler(PointerReleasedEvent, CanvasOnPointerReleased, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
|
||||||
_zoomBorder.AddHandler(PointerWheelChangedEvent, ZoomOnPointerWheelChanged, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
|
_zoomBorder.AddHandler(PointerWheelChangedEvent, ZoomOnPointerWheelChanged, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
|
||||||
|
_zoomBorder.AddHandler(PointerMovedEvent, ZoomOnPointerMoved, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
|
||||||
|
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
ViewModel!.AutoFitRequested += ViewModelOnAutoFitRequested;
|
ViewModel!.AutoFitRequested += ViewModelOnAutoFitRequested;
|
||||||
@ -67,6 +70,12 @@ public class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
|
|||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ZoomOnPointerMoved(object? sender, PointerEventArgs e)
|
||||||
|
{
|
||||||
|
if (ViewModel != null)
|
||||||
|
ViewModel.PastePosition = e.GetPosition(_grid);
|
||||||
|
}
|
||||||
|
|
||||||
private void ShowPickerAt(Point point)
|
private void ShowPickerAt(Point point)
|
||||||
{
|
{
|
||||||
if (ViewModel == null)
|
if (ViewModel == null)
|
||||||
|
|||||||
@ -6,9 +6,12 @@ using System.Linq;
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Reactive.Subjects;
|
using System.Reactive.Subjects;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Events;
|
using Artemis.Core.Events;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.UI.Models;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Screens.VisualScripting.Pins;
|
using Artemis.UI.Screens.VisualScripting.Pins;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
@ -16,6 +19,7 @@ using Artemis.UI.Shared.Services.NodeEditor;
|
|||||||
using Artemis.UI.Shared.Services.NodeEditor.Commands;
|
using Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.Mixins;
|
using Avalonia.Controls.Mixins;
|
||||||
|
using Avalonia.Input;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using DynamicData.Binding;
|
using DynamicData.Binding;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
@ -24,6 +28,8 @@ namespace Artemis.UI.Screens.VisualScripting;
|
|||||||
|
|
||||||
public class NodeScriptViewModel : ActivatableViewModelBase
|
public class NodeScriptViewModel : ActivatableViewModelBase
|
||||||
{
|
{
|
||||||
|
public const string CLIPBOARD_DATA_FORMAT = "Artemis.Nodes";
|
||||||
|
|
||||||
private readonly INodeEditorService _nodeEditorService;
|
private readonly INodeEditorService _nodeEditorService;
|
||||||
private readonly INodeService _nodeService;
|
private readonly INodeService _nodeService;
|
||||||
private readonly SourceList<NodeViewModel> _nodeViewModels;
|
private readonly SourceList<NodeViewModel> _nodeViewModels;
|
||||||
@ -33,6 +39,7 @@ public class NodeScriptViewModel : ActivatableViewModelBase
|
|||||||
private DragCableViewModel? _dragViewModel;
|
private DragCableViewModel? _dragViewModel;
|
||||||
private List<NodeViewModel>? _initialNodeSelection;
|
private List<NodeViewModel>? _initialNodeSelection;
|
||||||
private Matrix _panMatrix;
|
private Matrix _panMatrix;
|
||||||
|
private Point _pastePosition;
|
||||||
|
|
||||||
public NodeScriptViewModel(NodeScript nodeScript, bool isPreview, INodeVmFactory nodeVmFactory, INodeService nodeService, INodeEditorService nodeEditorService)
|
public NodeScriptViewModel(NodeScript nodeScript, bool isPreview, INodeVmFactory nodeVmFactory, INodeService nodeService, INodeEditorService nodeEditorService)
|
||||||
{
|
{
|
||||||
@ -86,8 +93,8 @@ public class NodeScriptViewModel : ActivatableViewModelBase
|
|||||||
ClearSelection = ReactiveCommand.Create(ExecuteClearSelection);
|
ClearSelection = ReactiveCommand.Create(ExecuteClearSelection);
|
||||||
DeleteSelected = ReactiveCommand.Create(ExecuteDeleteSelected);
|
DeleteSelected = ReactiveCommand.Create(ExecuteDeleteSelected);
|
||||||
DuplicateSelected = ReactiveCommand.Create(ExecuteDuplicateSelected);
|
DuplicateSelected = ReactiveCommand.Create(ExecuteDuplicateSelected);
|
||||||
CopySelected = ReactiveCommand.Create(ExecuteCopySelected);
|
CopySelected = ReactiveCommand.CreateFromTask(ExecuteCopySelected);
|
||||||
PasteSelected = ReactiveCommand.Create(ExecutePasteSelected);
|
PasteSelected = ReactiveCommand.CreateFromTask(ExecutePasteSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
public NodeScript NodeScript { get; }
|
public NodeScript NodeScript { get; }
|
||||||
@ -118,6 +125,12 @@ public class NodeScriptViewModel : ActivatableViewModelBase
|
|||||||
set => RaiseAndSetIfChanged(ref _panMatrix, value);
|
set => RaiseAndSetIfChanged(ref _panMatrix, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Point PastePosition
|
||||||
|
{
|
||||||
|
get => _pastePosition;
|
||||||
|
set => RaiseAndSetIfChanged(ref _pastePosition, value);
|
||||||
|
}
|
||||||
|
|
||||||
public void DeleteSelectedNodes()
|
public void DeleteSelectedNodes()
|
||||||
{
|
{
|
||||||
List<NodeViewModel> toRemove = NodeViewModels.Where(vm => vm.IsSelected && !vm.Node.IsDefaultNode && !vm.Node.IsExitNode).ToList();
|
List<NodeViewModel> toRemove = NodeViewModels.Where(vm => vm.IsSelected && !vm.Node.IsDefaultNode && !vm.Node.IsExitNode).ToList();
|
||||||
@ -279,11 +292,39 @@ public class NodeScriptViewModel : ActivatableViewModelBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExecuteCopySelected()
|
private async Task ExecuteCopySelected()
|
||||||
{
|
{
|
||||||
|
if (Application.Current?.Clipboard == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
List<INode> nodes = NodeViewModels.Where(vm => vm.IsSelected).Select(vm => vm.Node).Where(n => !n.IsDefaultNode && !n.IsExitNode).ToList();
|
||||||
|
DataObject dataObject = new();
|
||||||
|
string copy = CoreJson.SerializeObject(new NodesClipboardModel(NodeScript, nodes), true);
|
||||||
|
dataObject.Set(CLIPBOARD_DATA_FORMAT, copy);
|
||||||
|
await Application.Current.Clipboard.SetDataObjectAsync(dataObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExecutePasteSelected()
|
private async Task ExecutePasteSelected()
|
||||||
{
|
{
|
||||||
|
if (Application.Current?.Clipboard == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
byte[]? bytes = (byte[]?) await Application.Current.Clipboard.GetDataAsync(CLIPBOARD_DATA_FORMAT);
|
||||||
|
if (bytes == null!)
|
||||||
|
return;
|
||||||
|
|
||||||
|
NodesClipboardModel? nodesClipboardModel = CoreJson.DeserializeObject<NodesClipboardModel>(Encoding.Unicode.GetString(bytes), true);
|
||||||
|
if (nodesClipboardModel == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
List<INode> nodes = nodesClipboardModel.Paste(NodeScript, PastePosition.X, PastePosition.Y);
|
||||||
|
|
||||||
|
using NodeEditorCommandScope scope = _nodeEditorService.CreateCommandScope(NodeScript, "Paste nodes");
|
||||||
|
foreach (INode node in nodes)
|
||||||
|
_nodeEditorService.ExecuteCommand(NodeScript, new AddNode(NodeScript, node));
|
||||||
|
|
||||||
|
// Select only the new nodes
|
||||||
|
foreach (NodeViewModel nodeViewModel in NodeViewModels)
|
||||||
|
nodeViewModel.IsSelected = nodes.Contains(nodeViewModel.Node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user