mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 03:48:56 +00:00
Node editor - Added menu
Node scripts - Fixed IDs not being set for regular nodes Enums node - Hardened VM logic against weird connections
This commit is contained in:
1 parent
a0260b53e5
commit
850346ccd2
14 files changed
+352
-56
No files matched your search
+6
-8
@@ -16,6 +16,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes;
|
||||
public class EventConditionViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly EventCondition _eventCondition;
|
||||
private readonly INodeService _nodeService;
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ObservableAsPropertyHelper<bool> _showOverlapOptions;
|
||||
@@ -26,18 +27,15 @@ public class EventConditionViewModel : ActivatableViewModelBase
|
||||
private ObservableAsPropertyHelper<int>? _selectedToggleOffMode;
|
||||
private ObservableAsPropertyHelper<int>? _selectedTriggerMode;
|
||||
|
||||
public EventConditionViewModel(EventCondition eventCondition, IProfileEditorService profileEditorService, IWindowService windowService, ISettingsService settingsService)
|
||||
public EventConditionViewModel(EventCondition eventCondition, IProfileEditorService profileEditorService, INodeService nodeService, IWindowService windowService, ISettingsService settingsService)
|
||||
{
|
||||
_eventCondition = eventCondition;
|
||||
_profileEditorService = profileEditorService;
|
||||
_nodeService = nodeService;
|
||||
_windowService = windowService;
|
||||
_settingsService = settingsService;
|
||||
_showOverlapOptions = this.WhenAnyValue(vm => vm.SelectedTriggerMode)
|
||||
.Select(m => m == 0)
|
||||
.ToProperty(this, vm => vm.ShowOverlapOptions);
|
||||
_showToggleOffOptions = this.WhenAnyValue(vm => vm.SelectedTriggerMode)
|
||||
.Select(m => m == 1)
|
||||
.ToProperty(this, vm => vm.ShowToggleOffOptions);
|
||||
_showOverlapOptions = this.WhenAnyValue(vm => vm.SelectedTriggerMode).Select(m => m == 0).ToProperty(this, vm => vm.ShowOverlapOptions);
|
||||
_showToggleOffOptions = this.WhenAnyValue(vm => vm.SelectedTriggerMode).Select(m => m == 1).ToProperty(this, vm => vm.ShowToggleOffOptions);
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
@@ -82,6 +80,6 @@ public class EventConditionViewModel : ActivatableViewModelBase
|
||||
|
||||
private async Task ExecuteOpenEditor()
|
||||
{
|
||||
await _windowService.ShowDialogAsync<NodeScriptWindowViewModel, bool>(("nodeScript", _eventCondition.NodeScript));
|
||||
await _windowService.ShowDialogAsync<NodeScriptWindowViewModel, bool>(("nodeScript", _eventCondition.Script));
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -51,6 +51,6 @@ public class StaticConditionViewModel : ActivatableViewModelBase
|
||||
|
||||
private async Task ExecuteOpenEditor()
|
||||
{
|
||||
await _windowService.ShowDialogAsync<NodeScriptWindowViewModel, bool>(("nodeScript", _staticCondition.NodeScript));
|
||||
await _windowService.ShowDialogAsync<NodeScriptWindowViewModel, bool>(("nodeScript", _staticCondition.Script));
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ using Artemis.UI.Screens.Sidebar;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
using Newtonsoft.Json;
|
||||
using ReactiveUI;
|
||||
using Serilog;
|
||||
@@ -27,10 +26,10 @@ public class MenuBarViewModel : ActivatableViewModelBase
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IWindowService _windowService;
|
||||
private ProfileEditorHistory? _history;
|
||||
private ObservableAsPropertyHelper<bool>? _suspendedEditing;
|
||||
private ObservableAsPropertyHelper<bool>? _isSuspended;
|
||||
private ObservableAsPropertyHelper<ProfileConfiguration?>? _profileConfiguration;
|
||||
private ObservableAsPropertyHelper<RenderProfileElement?>? _profileElement;
|
||||
private ObservableAsPropertyHelper<bool>? _suspendedEditing;
|
||||
|
||||
public MenuBarViewModel(ILogger logger, IProfileEditorService profileEditorService, IProfileService profileService, ISettingsService settingsService, IWindowService windowService)
|
||||
{
|
||||
@@ -62,26 +61,26 @@ public class MenuBarViewModel : ActivatableViewModelBase
|
||||
DuplicateProfile = ReactiveCommand.Create(ExecuteDuplicateProfile, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null));
|
||||
ToggleSuspendedEditing = ReactiveCommand.Create(ExecuteToggleSuspendedEditing);
|
||||
ToggleBooleanSetting = ReactiveCommand.Create<PluginSetting<bool>>(ExecuteToggleBooleanSetting);
|
||||
OpenUri = ReactiveCommand.CreateFromTask<string>(ExecuteOpenUri);
|
||||
OpenUri = ReactiveCommand.Create<string>(s => Process.Start(new ProcessStartInfo(s) {UseShellExecute = true, Verb = "open"}));
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit, Unit> AddFolder { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddLayer { get; }
|
||||
public ReactiveCommand<Unit, Unit> ToggleSuspended { get; }
|
||||
public ReactiveCommand<Unit, Unit> ViewProperties { get; }
|
||||
public ReactiveCommand<Unit,Unit> AdaptProfile { get; }
|
||||
public ReactiveCommand<Unit, Unit> AdaptProfile { get; }
|
||||
public ReactiveCommand<Unit, Unit> DeleteProfile { get; }
|
||||
public ReactiveCommand<Unit, Unit> ExportProfile { get; }
|
||||
public ReactiveCommand<Unit, Unit> DuplicateProfile { get; }
|
||||
public ReactiveCommand<PluginSetting<bool>, Unit> ToggleBooleanSetting { get; }
|
||||
public ReactiveCommand<string, Unit> OpenUri { get; }
|
||||
public ReactiveCommand<Unit, Unit> ToggleSuspendedEditing { get; }
|
||||
|
||||
|
||||
public ProfileConfiguration? ProfileConfiguration => _profileConfiguration?.Value;
|
||||
public RenderProfileElement? ProfileElement => _profileElement?.Value;
|
||||
public bool IsSuspended => _isSuspended?.Value ?? false;
|
||||
public bool SuspendedEditing => _suspendedEditing?.Value ?? false;
|
||||
|
||||
|
||||
public PluginSetting<bool> AutoSuspend => _settingsService.GetSetting("ProfileEditor.AutoSuspend", true);
|
||||
public PluginSetting<bool> FocusSelectedLayer => _settingsService.GetSetting("ProfileEditor.FocusSelectedLayer", false);
|
||||
public PluginSetting<bool> ShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
|
||||
@@ -123,6 +122,7 @@ public class MenuBarViewModel : ActivatableViewModelBase
|
||||
("profileConfiguration", ProfileConfiguration)
|
||||
);
|
||||
}
|
||||
|
||||
private async Task ExecuteAdaptProfile()
|
||||
{
|
||||
if (ProfileConfiguration?.Profile == null)
|
||||
@@ -190,28 +190,15 @@ public class MenuBarViewModel : ActivatableViewModelBase
|
||||
ProfileConfigurationExportModel export = _profileService.ExportProfile(ProfileConfiguration);
|
||||
_profileService.ImportProfile(ProfileConfiguration.Category, export, true, false, "copy");
|
||||
}
|
||||
|
||||
|
||||
private void ExecuteToggleSuspendedEditing()
|
||||
{
|
||||
_profileEditorService.ChangeSuspendedEditing(!SuspendedEditing);
|
||||
}
|
||||
|
||||
|
||||
private void ExecuteToggleBooleanSetting(PluginSetting<bool> setting)
|
||||
{
|
||||
setting.Value = !setting.Value;
|
||||
setting.Save();
|
||||
}
|
||||
|
||||
private async Task ExecuteOpenUri(string uri)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(uri) {UseShellExecute = true, Verb = "open"});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(e, "Failed to open URL");
|
||||
await _windowService.ShowConfirmContentDialog("Failed to open URL", "We couldn't open the URL for you, check the logs for more details", "Confirm", null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ public class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
|
||||
_zoomBorder.AddHandler(PointerWheelChangedEvent, ZoomOnPointerWheelChanged, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
ViewModel.AutoFitRequested += ViewModelOnAutoFitRequested;
|
||||
ViewModel!.PickerPositionSubject.Subscribe(ShowPickerAt).DisposeWith(d);
|
||||
if (ViewModel.IsPreview)
|
||||
{
|
||||
@@ -53,6 +54,7 @@ public class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(() => AutoFit(true), DispatcherPriority.ContextIdle);
|
||||
Disposable.Create(() => ViewModel.AutoFitRequested -= ViewModelOnAutoFitRequested).DisposeWith(d);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -117,6 +119,11 @@ public class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
|
||||
_zoomBorder.Pan(Bounds.Center.X - scriptRect.Center.X * scale, Bounds.Center.Y - scriptRect.Center.Y * scale, skipTransitions);
|
||||
}
|
||||
|
||||
private void ViewModelOnAutoFitRequested(object? sender, EventArgs e)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() => AutoFit(false));
|
||||
}
|
||||
|
||||
private void ZoomBorderOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.Property.Name == nameof(_zoomBorder.Background))
|
||||
@@ -136,6 +143,8 @@ public class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
|
||||
|
||||
private void ZoomBorder_OnZoomChanged(object sender, ZoomChangedEventArgs e)
|
||||
{
|
||||
if (ViewModel != null)
|
||||
ViewModel.PanMatrix = _zoomBorder.Matrix;
|
||||
UpdateZoomBorderBackground();
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ public class NodeScriptViewModel : ActivatableViewModelBase
|
||||
|
||||
private DragCableViewModel? _dragViewModel;
|
||||
private List<NodeViewModel>? _initialNodeSelection;
|
||||
private Matrix _panMatrix;
|
||||
|
||||
public NodeScriptViewModel(NodeScript nodeScript, bool isPreview, INodeVmFactory nodeVmFactory, INodeService nodeService, INodeEditorService nodeEditorService)
|
||||
{
|
||||
@@ -95,6 +96,12 @@ public class NodeScriptViewModel : ActivatableViewModelBase
|
||||
get => _dragViewModel;
|
||||
set => RaiseAndSetIfChanged(ref _dragViewModel, value);
|
||||
}
|
||||
|
||||
public Matrix PanMatrix
|
||||
{
|
||||
get => _panMatrix;
|
||||
set => RaiseAndSetIfChanged(ref _panMatrix, value);
|
||||
}
|
||||
|
||||
public void DeleteSelectedNodes()
|
||||
{
|
||||
@@ -223,4 +230,11 @@ public class NodeScriptViewModel : ActivatableViewModelBase
|
||||
if (toRemove != null)
|
||||
_nodeViewModels.Remove(toRemove);
|
||||
}
|
||||
|
||||
public void RequestAutoFit()
|
||||
{
|
||||
AutoFitRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public event EventHandler? AutoFitRequested;
|
||||
}
|
||||
@@ -4,18 +4,163 @@
|
||||
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:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.VisualScripting.NodeScriptWindowView"
|
||||
x:DataType="visualScripting:NodeScriptWindowViewModel"
|
||||
Title="Artemis | Visual Script Editor"
|
||||
Width="1200"
|
||||
Height="800">
|
||||
<Grid Margin="15" ColumnDefinitions="*,*" RowDefinitions="Auto,*">
|
||||
<StackPanel Grid.Row="0">
|
||||
<TextBlock Classes="h4" Text="{CompiledBinding NodeScript.Name}"/>
|
||||
<TextBlock Classes="subtitle" Margin="0 0 0 10" Text="{CompiledBinding NodeScript.Description}"/>
|
||||
<Window.KeyBindings>
|
||||
<KeyBinding Command="{CompiledBinding History.Undo}" Gesture="Ctrl+Z" />
|
||||
<KeyBinding Command="{CompiledBinding History.Redo}" Gesture="Ctrl+Y" />
|
||||
</Window.KeyBindings>
|
||||
<Grid Margin="15" ColumnDefinitions="*,*" RowDefinitions="Auto,Auto,*">
|
||||
<Menu Grid.Row="0" Grid.ColumnSpan="2" VerticalAlignment="Top" Margin="-10 -10 -10 0">
|
||||
<MenuItem Header="_File">
|
||||
<MenuItem Header="Add Node" Items="{CompiledBinding Categories}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="Plus" />
|
||||
</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="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>
|
||||
<Separator />
|
||||
<MenuItem Header="Export Script" Command="{CompiledBinding Export}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="Export" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Import Script" Command="{CompiledBinding Import}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="Import" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Edit">
|
||||
<MenuItem Header="_Undo" Command="{CompiledBinding History.Undo}" InputGesture="Ctrl+Z">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="Undo" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Redo" Command="{CompiledBinding History.Redo}" InputGesture="Ctrl+Y">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="Redo" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="_Duplicate" Command="{Binding Duplicate}" InputGesture="Ctrl+D">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="ContentDuplicate" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Copy" Command="{Binding Copy}" InputGesture="Ctrl+C">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="ContentCopy" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Paste" Command="{Binding Paste}" InputGesture="Ctrl+V">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIconExt Kind="ContentPaste" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Options">
|
||||
<MenuItem Header="Display Data Model Values"
|
||||
Command="{CompiledBinding ToggleBooleanSetting}"
|
||||
CommandParameter="{CompiledBinding ShowDataModelValues}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="Check" IsVisible="{CompiledBinding ShowDataModelValues.Value}" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Display Full Condition Paths"
|
||||
Command="{CompiledBinding ToggleBooleanSetting}"
|
||||
CommandParameter="{CompiledBinding ShowFullPaths}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="Check" IsVisible="{CompiledBinding ShowFullPaths.Value}" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Always Display Cable Values"
|
||||
ToolTip.Tip="If enabled, cable values are always shown instead of only on hover"
|
||||
Command="{CompiledBinding ToggleBooleanSetting}"
|
||||
CommandParameter="{CompiledBinding AlwaysShowValues}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="Check" IsVisible="{CompiledBinding AlwaysShowValues.Value}" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Help">
|
||||
<MenuItem Header="Artemis Wiki" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="BookEdit" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="Editor" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/en/guides/user/profiles">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="Edit" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Layers" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/layers">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="Layers" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Display Conditions" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/conditions">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="NotEqual" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Timeline" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/timeline">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="Stopwatch" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Data Bindings" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/data-bindings">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="VectorLink" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Scripting" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/scripting">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="CodeJson" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="Report a Bug" Command="{CompiledBinding OpenUri}" CommandParameter="https://github.com/Artemis-RGB/Artemis/issues">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="Github" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Get Help on Discord" Command="{CompiledBinding OpenUri}" CommandParameter="https://discord.gg/S3MVaC9">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="Discord" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<StackPanel Grid.Row="1" Grid.Column="0" Orientation="Horizontal">
|
||||
<TextBlock Classes="h4" Text="{CompiledBinding NodeScript.Name}" />
|
||||
<TextBlock Classes="subtitle" Margin="10 0 0 13" Text="{CompiledBinding NodeScript.Description}" VerticalAlignment="Bottom" />
|
||||
</StackPanel>
|
||||
<controls:HyperlinkButton Grid.Row="0"
|
||||
<controls:HyperlinkButton Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right"
|
||||
@@ -23,7 +168,7 @@
|
||||
Learn more about visual scripts
|
||||
</controls:HyperlinkButton>
|
||||
|
||||
<Border Classes="card" Grid.Row="1" Grid.ColumnSpan="2">
|
||||
<Border Classes="card-condensed" Grid.Row="2" Grid.ColumnSpan="2">
|
||||
<ContentControl Content="{CompiledBinding NodeScriptViewModel}" />
|
||||
</Border>
|
||||
|
||||
|
||||
@@ -1,18 +1,122 @@
|
||||
using Artemis.Core;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reactive;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.UI.Shared.Services.NodeEditor;
|
||||
using Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||
using Avalonia;
|
||||
using DynamicData;
|
||||
using DynamicData.List;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.VisualScripting
|
||||
namespace Artemis.UI.Screens.VisualScripting;
|
||||
|
||||
public class NodeScriptWindowViewModel : DialogViewModelBase<bool>
|
||||
{
|
||||
public class NodeScriptWindowViewModel : DialogViewModelBase<bool>
|
||||
{
|
||||
public NodeScript NodeScript { get; }
|
||||
public NodeScriptViewModel NodeScriptViewModel { get; set; }
|
||||
private readonly INodeService _nodeService;
|
||||
private readonly INodeEditorService _nodeEditorService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IWindowService _windowService;
|
||||
|
||||
public NodeScriptWindowViewModel(NodeScript nodeScript, INodeVmFactory vmFactory)
|
||||
{
|
||||
NodeScript = nodeScript;
|
||||
NodeScriptViewModel = vmFactory.NodeScriptViewModel(NodeScript, false);
|
||||
}
|
||||
public NodeScriptWindowViewModel(NodeScript nodeScript,
|
||||
INodeService nodeService,
|
||||
INodeEditorService nodeEditorService,
|
||||
INodeVmFactory vmFactory,
|
||||
ISettingsService settingsService,
|
||||
IWindowService windowService)
|
||||
{
|
||||
NodeScript = nodeScript;
|
||||
NodeScriptViewModel = vmFactory.NodeScriptViewModel(NodeScript, false);
|
||||
OpenUri = ReactiveCommand.Create<string>(s => Process.Start(new ProcessStartInfo(s) {UseShellExecute = true, Verb = "open"}));
|
||||
ToggleBooleanSetting = ReactiveCommand.Create<PluginSetting<bool>>(ExecuteToggleBooleanSetting);
|
||||
History = nodeEditorService.GetHistory(nodeScript);
|
||||
|
||||
_nodeService = nodeService;
|
||||
_nodeEditorService = nodeEditorService;
|
||||
_settingsService = settingsService;
|
||||
_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);
|
||||
Export = ReactiveCommand.CreateFromTask(ExecuteExport);
|
||||
Import = ReactiveCommand.CreateFromTask(ExecuteImport);
|
||||
}
|
||||
|
||||
public NodeScript NodeScript { get; }
|
||||
public NodeScriptViewModel NodeScriptViewModel { get; set; }
|
||||
|
||||
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 ReactiveCommand<NodeData, Unit> CreateNode { get; }
|
||||
public ReactiveCommand<Unit, Unit> Export { get; }
|
||||
public ReactiveCommand<Unit, Unit> Import { get; }
|
||||
|
||||
public PluginSetting<bool> ShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
|
||||
public PluginSetting<bool> ShowFullPaths => _settingsService.GetSetting("ProfileEditor.ShowFullPaths", false);
|
||||
public PluginSetting<bool> AlwaysShowValues => _settingsService.GetSetting("ProfileEditor.AlwaysShowValues", false);
|
||||
|
||||
private void ExecuteToggleBooleanSetting(PluginSetting<bool> setting)
|
||||
{
|
||||
setting.Value = !setting.Value;
|
||||
setting.Save();
|
||||
}
|
||||
|
||||
private void ExecuteCreateNode(NodeData data)
|
||||
{
|
||||
// Place the node in the top-left of the canvas, keeping in mind the user may have panned
|
||||
INode node = data.CreateNode(NodeScript, null);
|
||||
Point point = new Point(0, 0).Transform(NodeScriptViewModel.PanMatrix.Invert());
|
||||
|
||||
node.X = Math.Round(point.X / 10d, 0, MidpointRounding.AwayFromZero) * 10d + 20;
|
||||
node.Y = Math.Round(point.Y / 10d, 0, MidpointRounding.AwayFromZero) * 10d + 20;
|
||||
|
||||
_nodeEditorService.ExecuteCommand(NodeScript, new AddNode(NodeScript, node));
|
||||
}
|
||||
|
||||
private async Task ExecuteExport()
|
||||
{
|
||||
// Might not cover everything but then the dialog will complain and that's good enough
|
||||
string? result = await _windowService.CreateSaveFileDialog()
|
||||
.HavingFilter(f => f.WithExtension("json").WithName("Artemis node script"))
|
||||
.ShowAsync();
|
||||
|
||||
if (result == null)
|
||||
return;
|
||||
|
||||
string json = _nodeService.ExportScript(NodeScript);
|
||||
await File.WriteAllTextAsync(result, json);
|
||||
}
|
||||
|
||||
private async Task ExecuteImport()
|
||||
{
|
||||
string[]? result = await _windowService.CreateOpenFileDialog()
|
||||
.HavingFilter(f => f.WithExtension("json").WithName("Artemis node script"))
|
||||
.ShowAsync();
|
||||
|
||||
if (result == null)
|
||||
return;
|
||||
|
||||
string json = await File.ReadAllTextAsync(result[0]);
|
||||
_nodeService.ImportScript(json, NodeScript);
|
||||
History.Clear();
|
||||
|
||||
await Task.Delay(200);
|
||||
NodeScriptViewModel.RequestAutoFit();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user