mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-12 21:38:38 +00:00
187 lines
6.9 KiB
C#
187 lines
6.9 KiB
C#
using System;
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Reactive;
|
|
using System.Reactive.Disposables;
|
|
using System.Threading.Tasks;
|
|
using System.Timers;
|
|
using Artemis.Core;
|
|
using Artemis.Core.Services;
|
|
using Artemis.UI.DryIoc.Factories;
|
|
using Artemis.UI.Shared.Services;
|
|
using Artemis.UI.Shared.Services.NodeEditor;
|
|
using Artemis.UI.Shared.Services.NodeEditor.Commands;
|
|
using Avalonia;
|
|
using DynamicData;
|
|
using ReactiveUI;
|
|
|
|
namespace Artemis.UI.Screens.VisualScripting;
|
|
|
|
public class NodeScriptWindowViewModel : NodeScriptWindowViewModelBase
|
|
{
|
|
private readonly INodeEditorService _nodeEditorService;
|
|
private readonly INodeService _nodeService;
|
|
private readonly IProfileService _profileService;
|
|
private readonly ISettingsService _settingsService;
|
|
private readonly IWindowService _windowService;
|
|
private bool _pauseUpdate;
|
|
private ObservableAsPropertyHelper<bool>? _keyBindingsEnabled;
|
|
|
|
public NodeScriptWindowViewModel(NodeScript nodeScript,
|
|
INodeService nodeService,
|
|
INodeEditorService nodeEditorService,
|
|
INodeVmFactory vmFactory,
|
|
ISettingsService settingsService,
|
|
IProfileService profileService,
|
|
IWindowService windowService) : base(nodeScript)
|
|
{
|
|
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;
|
|
_profileService = profileService;
|
|
_windowService = windowService;
|
|
|
|
CreateNode = ReactiveCommand.Create<NodeData>(ExecuteCreateNode);
|
|
AutoArrange = ReactiveCommand.CreateFromTask(ExecuteAutoArrange, this.WhenAnyValue(vm => vm.KeyBindingsEnabled));
|
|
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 =>
|
|
{
|
|
_keyBindingsEnabled = Shared.UI.CurrentKeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d);
|
|
|
|
Timer updateTimer = new(TimeSpan.FromMilliseconds(25.0 / 1000));
|
|
Timer saveTimer = new(TimeSpan.FromMinutes(2));
|
|
|
|
updateTimer.Elapsed += (_, _) => Update();
|
|
saveTimer.Elapsed += (_, _) => Save();
|
|
updateTimer.Start();
|
|
saveTimer.Start();
|
|
|
|
updateTimer.DisposeWith(d);
|
|
saveTimer.DisposeWith(d);
|
|
});
|
|
}
|
|
|
|
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<NodeMenuItemViewModel> Categories { get; }
|
|
public ReactiveCommand<NodeData, Unit> CreateNode { get; }
|
|
public ReactiveCommand<Unit, Unit> AutoArrange { get; }
|
|
public ReactiveCommand<Unit, Unit> Export { get; }
|
|
public ReactiveCommand<Unit, Unit> Import { get; }
|
|
public bool KeyBindingsEnabled => _keyBindingsEnabled?.Value ?? false;
|
|
|
|
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", true);
|
|
|
|
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 ExecuteAutoArrange()
|
|
{
|
|
try
|
|
{
|
|
if (!NodeScript.ExitNodeConnected)
|
|
{
|
|
await _windowService.ShowConfirmContentDialog("Cannot auto-arrange", "The exit node must be connected in order to perform auto-arrange.", "Close", null);
|
|
return;
|
|
}
|
|
|
|
_pauseUpdate = true;
|
|
_nodeEditorService.ExecuteCommand(NodeScript, new OrganizeScript(NodeScript));
|
|
await Task.Delay(200);
|
|
NodeScriptViewModel.RequestAutoFit();
|
|
}
|
|
finally
|
|
{
|
|
_pauseUpdate = false;
|
|
}
|
|
}
|
|
|
|
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 || result.Length == 0)
|
|
return;
|
|
|
|
try
|
|
{
|
|
_pauseUpdate = true;
|
|
string json = await File.ReadAllTextAsync(result[0]);
|
|
_nodeService.ImportScript(json, NodeScript);
|
|
History.Clear();
|
|
|
|
await Task.Delay(200);
|
|
NodeScriptViewModel.RequestAutoFit();
|
|
}
|
|
finally
|
|
{
|
|
_pauseUpdate = false;
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!_pauseUpdate)
|
|
NodeScript.Run();
|
|
}
|
|
|
|
private void Save()
|
|
{
|
|
if (NodeScript.Context is Profile profile)
|
|
_profileService.SaveProfile(profile, true);
|
|
}
|
|
} |