mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Visual scripting fixes
This commit is contained in:
parent
82b41425aa
commit
5a5a6819b1
@ -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>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
@ -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"
|
||||
@ -19,13 +18,14 @@
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Content=""
|
||||
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=""
|
||||
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>
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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;
|
||||
@ -88,3 +98,24 @@ 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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user