1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 21:38:38 +00:00

Visual scripting fixes

This commit is contained in:
Robert 2023-04-02 20:21:01 +02:00
parent 82b41425aa
commit 5a5a6819b1
11 changed files with 167 additions and 80 deletions

View File

@ -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 @@
<shared:SKColorToStringConverter x:Key="SKColorToStringConverter" />
<shared:SKColorToColorConverter x:Key="SKColorToColorConverter" />
</UserControl.Resources>
<Canvas PointerEntered="OnPointerEnter"
PointerExited="OnPointerLeave">
<Canvas PointerEntered="OnPointerEntered"
PointerExited="OnPointerExited">
<Path Name="CablePath"
Stroke="{CompiledBinding CableColor, Converter={StaticResource ColorToSolidColorBrushConverter}}"
StrokeThickness="4"
StrokeLineCap="Round">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure IsClosed="False">
<PathFigure.Segments>
<BezierSegment />
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
<Border Name="ValueBorder"
Background="{DynamicResource ContentDialogBackground}"
@ -79,7 +69,7 @@
<DataTemplate DataType="collections:IList">
<TextBlock Text="{Binding Count, StringFormat='List - {0} item(s)', Mode=OneWay}" FontFamily="Consolas" />
</DataTemplate>
<DataTemplate>
<DataTemplate DataType="system:Object">
<TextBlock Text="{Binding Mode=OneWay}" FontFamily="Consolas" />
</DataTemplate>
</ContentControl.DataTemplates>

View File

@ -24,39 +24,48 @@ public partial class CableView : ReactiveUserControl<CableViewModel>
{
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);
}

View File

@ -29,14 +29,28 @@ public partial class DragCableView : ReactiveUserControl<DragCableViewModel>
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;
}
}

View File

@ -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<NodeData,string> category)
{
Category = category.Key;
Nodes = category.Items.ToList();
}
public string Category { get; set; }
public List<NodeData> Nodes { get; set; }
}

View File

@ -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<NodeData, Unit> createNode, DynamicData.List.IGrouping<NodeData, string> category)
{
Header = category.Key;
Items = category.Items.Select(d => new NodeMenuItemViewModel(createNode, d)).ToList();
}
public NodeMenuItemViewModel(ReactiveCommand<NodeData, Unit> createNode, NodeData nodeData)
{
Header = nodeData.Name;
Items = new List<NodeMenuItemViewModel>();
CreateNode = ReactiveCommand.Create(() => { createNode.Execute(nodeData).Subscribe(); });
}
public string Header { get; }
public List<NodeMenuItemViewModel> Items { get; }
public ReactiveCommand<Unit, Unit>? CreateNode { get; }
}

View File

@ -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 @@
<Setter Property="InnerRightContent">
<Template>
<StackPanel Orientation="Horizontal">
<Button Content="&#xE8BB;"
<Button Content="&#xE8BB;"
FontFamily="{StaticResource SymbolThemeFontFamily}"
Classes="AppBarButton"
Theme="{StaticResource TransparentButton}"
Command="{Binding $parent[TextBox].Clear}"
IsVisible="{Binding $parent[TextBox].Text, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
IsVisible="{Binding Text, RelativeSource={RelativeSource FindAncestor, AncestorType=TextBox}, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
<Button Content="&#xE721;"
FontFamily="{StaticResource SymbolThemeFontFamily}"
Classes="AppBarButton"
IsHitTestVisible="False" />
Theme="{StaticResource TransparentButton}"
Command="{Binding $parent[TextBox].Clear}"
IsHitTestVisible="False"/>
</StackPanel>
</Template>
</Setter>
@ -46,16 +46,21 @@
</Style>
</TreeView.Styles>
<TreeView.DataTemplates>
<TreeDataTemplate DataType="{x:Type core:NodeData}">
<TreeDataTemplate DataType="core:NodeData">
<StackPanel Margin="-15 1 0 1" Background="Transparent" PointerReleased="InputElement_OnPointerReleased">
<TextBlock Classes="BodyStrongTextBlockStyle" Text="{Binding Name}" TextWrapping="Wrap"></TextBlock>
<TextBlock Foreground="{DynamicResource TextFillColorSecondary}" Text="{Binding Description}" TextWrapping="Wrap"></TextBlock>
</StackPanel>
</TreeDataTemplate>
<TreeDataTemplate ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Key}"></TextBlock>
<TreeDataTemplate DataType="visualScripting:NodeCategoryViewModel" ItemsSource="{Binding Nodes}">
<TextBlock Text="{Binding Category}"></TextBlock>
</TreeDataTemplate>
</TreeView.DataTemplates>
<TreeView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</TreeView.ItemsPanel>
</TreeView>
<StackPanel Grid.Row="1" VerticalAlignment="Center" Spacing="20" IsVisible="{CompiledBinding !Categories.Count}">
<avalonia:MaterialIcon Kind="CloseCircle" Width="64" Height="64"></avalonia:MaterialIcon>

View File

@ -42,7 +42,8 @@ public class NodePickerViewModel : ActivatableViewModelBase
.ThenByAscending(d => d.Category)
.ThenByAscending(d => d.Name))
.GroupWithImmutableState(n => n.Category)
.Bind(out ReadOnlyObservableCollection<DynamicData.List.IGrouping<NodeData, string>> categories)
.Transform(c => new NodeCategoryViewModel(c))
.Bind(out ReadOnlyObservableCollection<NodeCategoryViewModel> categories)
.Subscribe();
Categories = categories;
@ -62,7 +63,7 @@ public class NodePickerViewModel : ActivatableViewModelBase
});
}
public ReadOnlyObservableCollection<DynamicData.List.IGrouping<NodeData, string>> Categories { get; }
public ReadOnlyObservableCollection<NodeCategoryViewModel> Categories { get; }
public bool IsVisible
{

View File

@ -68,8 +68,9 @@ public partial class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
{
if (ViewModel == null)
return;
ViewModel.NodePickerViewModel.Position = point;
NodeScriptZoomBorder?.ContextFlyout?.ShowAt(NodeScriptZoomBorder);
ViewModel.NodePickerViewModel.Position = point;
}
private void AutoFitIfPreview()

View File

@ -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 @@
</MenuItem.Icon>
<MenuItem.Styles>
<Style Selector="MenuItem > MenuItem > MenuItem">
<Setter Property="Command" Value="{Binding $parent[visualScripting:NodeScriptWindowView].DataContext.CreateNode}" />
<Setter Property="CommandParameter" Value="{Binding}" />
<Setter Property="Command" Value="{Binding CreateNode}" />
<Setter Property="Header" Value="{Binding Header}" />
<Setter Property="Items" Value="{Binding Items}" />
</Style>
</MenuItem.Styles>
<MenuItem.DataTemplates>
<DataTemplate DataType="{x:Type core:NodeData}">
<StackPanel Background="Transparent">
<TextBlock Text="{Binding Name}" TextWrapping="Wrap"></TextBlock>
<TextBlock Foreground="{DynamicResource TextFillColorSecondary}" Text="{Binding Description}" TextWrapping="Wrap"></TextBlock>
</StackPanel>
</DataTemplate>
<DataTemplate>
<TextBlock Text="{Binding Key}"></TextBlock>
</DataTemplate>
</MenuItem.DataTemplates>
</MenuItem>
<MenuItem Header="Auto-arrange" Command="{CompiledBinding AutoArrange}" InputGesture="Ctrl+F">
<MenuItem.Icon>

View File

@ -48,19 +48,20 @@ public class NodeScriptWindowViewModel : NodeScriptWindowViewModelBase
_profileService = profileService;
_windowService = windowService;
SourceList<NodeData> nodeSourceList = new();
nodeSourceList.AddRange(nodeService.AvailableNodes);
nodeSourceList.Connect()
.GroupWithImmutableState(n => n.Category)
.Bind(out ReadOnlyObservableCollection<IGrouping<NodeData, string>> categories)
.Subscribe();
Categories = categories;
CreateNode = ReactiveCommand.Create<NodeData>(ExecuteCreateNode);
AutoArrange = ReactiveCommand.CreateFromTask(ExecuteAutoArrange);
Export = ReactiveCommand.CreateFromTask(ExecuteExport);
Import = ReactiveCommand.CreateFromTask(ExecuteImport);
SourceList<NodeData> nodeSourceList = new();
nodeSourceList.AddRange(nodeService.AvailableNodes);
nodeSourceList.Connect()
.GroupWithImmutableState(n => n.Category)
.Transform(c => new NodeMenuItemViewModel(CreateNode, c))
.Bind(out ReadOnlyObservableCollection<NodeMenuItemViewModel> 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<PluginSetting<bool>, Unit> ToggleBooleanSetting { get; set; }
public ReactiveCommand<string, Unit> OpenUri { get; set; }
public ReadOnlyObservableCollection<IGrouping<NodeData, string>> Categories { get; }
public ReadOnlyObservableCollection<NodeMenuItemViewModel> Categories { get; }
public ReactiveCommand<NodeData, Unit> CreateNode { get; }
public ReactiveCommand<Unit, Unit> AutoArrange { get; }
public ReactiveCommand<Unit, Unit> Export { get; }

View File

@ -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<PinViewModel>
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<PinViewModel>
{
base.OnAttachedToVisualTree(e);
_container = this.FindAncestorOfType<Canvas>();
AvaloniaLocator.Current.GetRequiredService<IRenderLoop>().Add(_renderLoopTask);
}
/// <inheritdoc />
public override void Render(DrawingContext context)
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.Render(context);
UpdatePosition();
base.OnDetachedFromVisualTree(e);
AvaloniaLocator.Current.GetRequiredService<IRenderLoop>().Remove(_renderLoopTask);
}
private void UpdatePosition()
public void UpdatePosition()
{
if (_container == null || _pinPoint == null || ViewModel == null)
return;
@ -87,4 +97,25 @@ public class PinView : ReactiveUserControl<PinViewModel>
}
#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;
}