diff --git a/src/Artemis.UI/Screens/VisualScripting/CableView.axaml b/src/Artemis.UI/Screens/VisualScripting/CableView.axaml index 30a6a4af5..da068c21e 100644 --- a/src/Artemis.UI/Screens/VisualScripting/CableView.axaml +++ b/src/Artemis.UI/Screens/VisualScripting/CableView.axaml @@ -8,6 +8,7 @@ xmlns:shared="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared" xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core" xmlns:collections="clr-namespace:System.Collections;assembly=System.Runtime" + xmlns:system="clr-namespace:System;assembly=System.Runtime" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.VisualScripting.CableView" x:DataType="visualScripting:CableViewModel" @@ -18,23 +19,12 @@ - + - - - - - - - - - - - - + diff --git a/src/Artemis.UI/Screens/VisualScripting/CableView.axaml.cs b/src/Artemis.UI/Screens/VisualScripting/CableView.axaml.cs index 7a8c82227..e6465474e 100644 --- a/src/Artemis.UI/Screens/VisualScripting/CableView.axaml.cs +++ b/src/Artemis.UI/Screens/VisualScripting/CableView.axaml.cs @@ -24,39 +24,48 @@ public partial class CableView : ReactiveUserControl { 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(true)).DisposeWith(d); - ViewModel.WhenAnyValue(vm => vm.ToPoint).Subscribe(_ => Update(false)).DisposeWith(d); - Update(true); + ViewModel.WhenAnyValue(vm => vm.FromPoint).Subscribe(_ => Update()).DisposeWith(d); + ViewModel.WhenAnyValue(vm => vm.ToPoint).Subscribe(_ => Update()).DisposeWith(d); + Update(); }); } - - private void Update(bool from) + private void Update() { - // Workaround for https://github.com/AvaloniaUI/Avalonia/issues/4748 - 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); + if (ViewModel == null) + return; - PathFigure pathFigure = ((PathGeometry) CablePath.Data).Figures.First(); - BezierSegment segment = (BezierSegment) pathFigure.Segments!.First(); - pathFigure.StartPoint = ViewModel!.FromPoint; - 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.Point3 = new Point(ViewModel.ToPoint.X, ViewModel.ToPoint.Y); + PathGeometry geometry = new() + { + Figures = new PathFigures() + }; + PathFigure pathFigure = new() + { + StartPoint = ViewModel.FromPoint, + IsClosed = false, + Segments = new PathSegments + { + new BezierSegment + { + Point1 = new Point(ViewModel.FromPoint.X + CABLE_OFFSET, ViewModel.FromPoint.Y), + Point2 = new Point(ViewModel.ToPoint.X - CABLE_OFFSET, ViewModel.ToPoint.Y), + Point3 = new Point(ViewModel.ToPoint.X, ViewModel.ToPoint.Y) + } + } + }; + geometry.Figures.Add(pathFigure); + CablePath.Data = geometry; 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 OnPointerEntered(object? sender, PointerEventArgs e) { ViewModel?.UpdateDisplayValue(true); } - private void OnPointerLeave(object? sender, PointerEventArgs e) + private void OnPointerExited(object? sender, PointerEventArgs e) { ViewModel?.UpdateDisplayValue(false); } diff --git a/src/Artemis.UI/Screens/VisualScripting/DragCableView.axaml.cs b/src/Artemis.UI/Screens/VisualScripting/DragCableView.axaml.cs index 8ba56ea89..885ba1996 100644 --- a/src/Artemis.UI/Screens/VisualScripting/DragCableView.axaml.cs +++ b/src/Artemis.UI/Screens/VisualScripting/DragCableView.axaml.cs @@ -29,14 +29,28 @@ public partial class DragCableView : ReactiveUserControl private void Update() { - PathFigure? pathFigure = ((PathGeometry) CablePath.Data).Figures?.FirstOrDefault(); - if (pathFigure?.Segments == null) + if (ViewModel == null) return; - BezierSegment segment = (BezierSegment) pathFigure.Segments.First(); - pathFigure.StartPoint = ViewModel!.FromPoint; - 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.Point3 = new Point(ViewModel.ToPoint.X, ViewModel.ToPoint.Y); + PathGeometry geometry = new() + { + Figures = new PathFigures() + }; + PathFigure pathFigure = new() + { + StartPoint = ViewModel.FromPoint, + IsClosed = false, + Segments = new PathSegments + { + new BezierSegment + { + Point1 = new Point(ViewModel.FromPoint.X + CABLE_OFFSET, ViewModel.FromPoint.Y), + Point2 = new Point(ViewModel.ToPoint.X - CABLE_OFFSET, ViewModel.ToPoint.Y), + Point3 = new Point(ViewModel.ToPoint.X, ViewModel.ToPoint.Y) + } + } + }; + geometry.Figures.Add(pathFigure); + CablePath.Data = geometry; } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/VisualScripting/NodeCategoryViewModel.cs b/src/Artemis.UI/Screens/VisualScripting/NodeCategoryViewModel.cs new file mode 100644 index 000000000..acc9e641b --- /dev/null +++ b/src/Artemis.UI/Screens/VisualScripting/NodeCategoryViewModel.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Linq; +using Artemis.Core; + +namespace Artemis.UI.Screens.VisualScripting; + +public class NodeCategoryViewModel +{ + public NodeCategoryViewModel(DynamicData.List.IGrouping category) + { + Category = category.Key; + Nodes = category.Items.ToList(); + } + + public string Category { get; set; } + public List Nodes { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/VisualScripting/NodeMenuItemViewModel.cs b/src/Artemis.UI/Screens/VisualScripting/NodeMenuItemViewModel.cs new file mode 100644 index 000000000..8ca0c9a41 --- /dev/null +++ b/src/Artemis.UI/Screens/VisualScripting/NodeMenuItemViewModel.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive; +using Artemis.Core; +using ReactiveUI; + +namespace Artemis.UI.Screens.VisualScripting; + +public class NodeMenuItemViewModel +{ + public NodeMenuItemViewModel(ReactiveCommand createNode, DynamicData.List.IGrouping category) + { + Header = category.Key; + Items = category.Items.Select(d => new NodeMenuItemViewModel(createNode, d)).ToList(); + } + + public NodeMenuItemViewModel(ReactiveCommand createNode, NodeData nodeData) + { + Header = nodeData.Name; + Items = new List(); + CreateNode = ReactiveCommand.Create(() => { createNode.Execute(nodeData).Subscribe(); }); + } + + public string Header { get; } + public List Items { get; } + public ReactiveCommand? CreateNode { get; } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml b/src/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml index 73065876d..c708afe0b 100644 --- a/src/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml +++ b/src/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml @@ -3,7 +3,6 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:visualScripting="clr-namespace:Artemis.UI.Screens.VisualScripting" - xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" mc:Ignorable="d" d:DesignWidth="650" d:DesignHeight="450" @@ -17,15 +16,16 @@ @@ -46,16 +46,21 @@ - + - - + + + + + + + diff --git a/src/Artemis.UI/Screens/VisualScripting/NodePickerViewModel.cs b/src/Artemis.UI/Screens/VisualScripting/NodePickerViewModel.cs index eb1f5d014..9612e2fb7 100644 --- a/src/Artemis.UI/Screens/VisualScripting/NodePickerViewModel.cs +++ b/src/Artemis.UI/Screens/VisualScripting/NodePickerViewModel.cs @@ -42,7 +42,8 @@ public class NodePickerViewModel : ActivatableViewModelBase .ThenByAscending(d => d.Category) .ThenByAscending(d => d.Name)) .GroupWithImmutableState(n => n.Category) - .Bind(out ReadOnlyObservableCollection> categories) + .Transform(c => new NodeCategoryViewModel(c)) + .Bind(out ReadOnlyObservableCollection categories) .Subscribe(); Categories = categories; @@ -62,7 +63,7 @@ public class NodePickerViewModel : ActivatableViewModelBase }); } - public ReadOnlyObservableCollection> Categories { get; } + public ReadOnlyObservableCollection Categories { get; } public bool IsVisible { diff --git a/src/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs b/src/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs index 47fa01493..1225ea76f 100644 --- a/src/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs +++ b/src/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs @@ -68,8 +68,9 @@ public partial class NodeScriptView : ReactiveUserControl { if (ViewModel == null) return; - ViewModel.NodePickerViewModel.Position = point; + NodeScriptZoomBorder?.ContextFlyout?.ShowAt(NodeScriptZoomBorder); + ViewModel.NodePickerViewModel.Position = point; } private void AutoFitIfPreview() diff --git a/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowView.axaml b/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowView.axaml index b55439e50..1d2de9c4a 100644 --- a/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowView.axaml +++ b/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowView.axaml @@ -7,6 +7,7 @@ xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core" xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia" + xmlns:system="clr-namespace:System;assembly=System.Runtime" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.VisualScripting.NodeScriptWindowView" x:DataType="visualScripting:NodeScriptWindowViewModel" @@ -28,22 +29,11 @@ - - - - - - - - - - - diff --git a/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowViewModel.cs b/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowViewModel.cs index 7412f8d84..37348d46d 100644 --- a/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowViewModel.cs +++ b/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowViewModel.cs @@ -48,19 +48,20 @@ public class NodeScriptWindowViewModel : NodeScriptWindowViewModelBase _profileService = profileService; _windowService = windowService; - SourceList nodeSourceList = new(); - nodeSourceList.AddRange(nodeService.AvailableNodes); - nodeSourceList.Connect() - .GroupWithImmutableState(n => n.Category) - .Bind(out ReadOnlyObservableCollection> categories) - .Subscribe(); - Categories = categories; - CreateNode = ReactiveCommand.Create(ExecuteCreateNode); AutoArrange = ReactiveCommand.CreateFromTask(ExecuteAutoArrange); Export = ReactiveCommand.CreateFromTask(ExecuteExport); Import = ReactiveCommand.CreateFromTask(ExecuteImport); + SourceList nodeSourceList = new(); + nodeSourceList.AddRange(nodeService.AvailableNodes); + nodeSourceList.Connect() + .GroupWithImmutableState(n => n.Category) + .Transform(c => new NodeMenuItemViewModel(CreateNode, c)) + .Bind(out ReadOnlyObservableCollection categories) + .Subscribe(); + Categories = categories; + this.WhenActivated(d => { DispatcherTimer updateTimer = new(TimeSpan.FromMilliseconds(25.0 / 1000), DispatcherPriority.Normal, Update); @@ -83,7 +84,7 @@ public class NodeScriptWindowViewModel : NodeScriptWindowViewModelBase public NodeEditorHistory History { get; } public ReactiveCommand, Unit> ToggleBooleanSetting { get; set; } public ReactiveCommand OpenUri { get; set; } - public ReadOnlyObservableCollection> Categories { get; } + public ReadOnlyObservableCollection Categories { get; } public ReactiveCommand CreateNode { get; } public ReactiveCommand AutoArrange { get; } public ReactiveCommand Export { get; } diff --git a/src/Artemis.UI/Screens/VisualScripting/Pins/PinView.cs b/src/Artemis.UI/Screens/VisualScripting/Pins/PinView.cs index 3ee05c759..2663efc64 100644 --- a/src/Artemis.UI/Screens/VisualScripting/Pins/PinView.cs +++ b/src/Artemis.UI/Screens/VisualScripting/Pins/PinView.cs @@ -1,8 +1,9 @@ -using Avalonia; +using System; +using Avalonia; using Avalonia.Controls; using Avalonia.Input; -using Avalonia.Media; using Avalonia.ReactiveUI; +using Avalonia.Rendering; using Avalonia.VisualTree; namespace Artemis.UI.Screens.VisualScripting.Pins; @@ -12,12 +13,20 @@ public class PinView : ReactiveUserControl private Canvas? _container; private bool _dragging; private Border? _pinPoint; + private PinViewRenderLoopTaks _renderLoopTask; protected void InitializePin(Border pinPoint) { _pinPoint = pinPoint; _pinPoint.PointerMoved += PinPointOnPointerMoved; _pinPoint.PointerReleased += PinPointOnPointerReleased; + _pinPoint.PropertyChanged += PinPointOnPropertyChanged; + _renderLoopTask = new PinViewRenderLoopTaks(this); + } + + private void PinPointOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) + { + Console.WriteLine(e); } private void PinPointOnPointerMoved(object? sender, PointerEventArgs e) @@ -67,16 +76,17 @@ public class PinView : ReactiveUserControl { base.OnAttachedToVisualTree(e); _container = this.FindAncestorOfType(); + AvaloniaLocator.Current.GetRequiredService().Add(_renderLoopTask); } /// - public override void Render(DrawingContext context) + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { - base.Render(context); - UpdatePosition(); + base.OnDetachedFromVisualTree(e); + AvaloniaLocator.Current.GetRequiredService().Remove(_renderLoopTask); } - private void UpdatePosition() + public void UpdatePosition() { if (_container == null || _pinPoint == null || ViewModel == null) return; @@ -87,4 +97,25 @@ public class PinView : ReactiveUserControl } #endregion +} + +public class PinViewRenderLoopTaks : IRenderLoopTask +{ + private readonly PinView _pinView; + + public PinViewRenderLoopTaks(PinView pinView) + { + _pinView = pinView; + } + + public void Update(TimeSpan time) + { + _pinView.UpdatePosition(); + } + + public void Render() + { + } + + public bool NeedsUpdate => true; } \ No newline at end of file