diff --git a/src/Artemis.Core/Services/ProcessMonitor/ProcessMonitorService.cs b/src/Artemis.Core/Services/ProcessMonitor/ProcessMonitorService.cs index e832df0b9..e5da142a7 100644 --- a/src/Artemis.Core/Services/ProcessMonitor/ProcessMonitorService.cs +++ b/src/Artemis.Core/Services/ProcessMonitor/ProcessMonitorService.cs @@ -11,17 +11,15 @@ namespace Artemis.Core.Services; internal class ProcessMonitorService : IProcessMonitorService { private readonly ProcessComparer _comparer; - private readonly ILogger _logger; private Process[] _lastScannedProcesses; - public ProcessMonitorService(ILogger logger) + public ProcessMonitorService() { - _logger = logger; + _comparer = new ProcessComparer(); _lastScannedProcesses = Process.GetProcesses(); Timer processScanTimer = new(1000); processScanTimer.Elapsed += OnTimerElapsed; processScanTimer.Start(); - _comparer = new ProcessComparer(); ProcessActivationRequirement.ProcessMonitorService = this; } @@ -30,16 +28,9 @@ internal class ProcessMonitorService : IProcessMonitorService { Process[] newProcesses = Process.GetProcesses(); foreach (Process startedProcess in newProcesses.Except(_lastScannedProcesses, _comparer)) - { ProcessStarted?.Invoke(this, new ProcessEventArgs(startedProcess)); - //_logger.Verbose("Started Process: {startedProcess}", startedProcess.ProcessName); - } - foreach (Process stoppedProcess in _lastScannedProcesses.Except(newProcesses, _comparer)) - { ProcessStopped?.Invoke(this, new ProcessEventArgs(stoppedProcess)); - //_logger.Verbose("Stopped Process: {stoppedProcess}", stoppedProcess.ProcessName); - } _lastScannedProcesses = newProcesses; } diff --git a/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs b/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs index c45fbdb96..456692a4d 100644 --- a/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs +++ b/src/Artemis.Core/VisualScripting/Interfaces/INodeScript.cs @@ -8,7 +8,7 @@ namespace Artemis.Core; /// /// Represents a node script /// -public interface INodeScript : INotifyPropertyChanged, IDisposable +public interface INodeScript : INotifyPropertyChanged, IDisposable, IStorageModel { /// /// Gets the name of the node script. @@ -66,6 +66,11 @@ public interface INodeScript : INotifyPropertyChanged, IDisposable /// /// The node to remove void RemoveNode(INode node); + + /// + /// Loads missing connections between the nodes of this node script from storage + /// + void LoadConnections(); } /// diff --git a/src/Artemis.Core/VisualScripting/NodeScript.cs b/src/Artemis.Core/VisualScripting/NodeScript.cs index ce6a7af00..8abc29fbd 100644 --- a/src/Artemis.Core/VisualScripting/NodeScript.cs +++ b/src/Artemis.Core/VisualScripting/NodeScript.cs @@ -12,7 +12,7 @@ namespace Artemis.Core; /// /// Represents a node script /// -public abstract class NodeScript : CorePropertyChanged, INodeScript, IStorageModel +public abstract class NodeScript : CorePropertyChanged, INodeScript { private void NodeTypeStoreOnNodeTypeChanged(object? sender, NodeTypeStoreEvent e) { @@ -226,9 +226,7 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript, IStorageMod return node; } - /// - /// Loads missing connections between the nodes of this node script from the - /// + /// public void LoadConnections() { List nodes = Nodes.ToList(); diff --git a/src/Artemis.UI.Linux/Providers/Input/LinuxInputProvider.cs b/src/Artemis.UI.Linux/Providers/Input/LinuxInputProvider.cs index c903a1d84..26c5c36ed 100644 --- a/src/Artemis.UI.Linux/Providers/Input/LinuxInputProvider.cs +++ b/src/Artemis.UI.Linux/Providers/Input/LinuxInputProvider.cs @@ -33,12 +33,14 @@ public class LinuxInputProvider : InputProvider protected override void Dispose(bool disposing) { if (disposing) + { for (int i = _readers.Count - 1; i >= 0; i--) { _readers[i].InputEvent -= OnInputEvent; _readers[i].Dispose(); _readers.RemoveAt(i); } + } base.Dispose(disposing); } @@ -49,6 +51,7 @@ public class LinuxInputProvider : InputProvider { if (sender is not LinuxInputDeviceReader reader) return; + switch (reader.InputDevice.DeviceType) { case LinuxDeviceType.Keyboard: @@ -64,40 +67,34 @@ public class LinuxInputProvider : InputProvider private void HandleKeyboardData(LinuxInputDevice keyboard, LinuxInputEventArgs args) { - switch (args.Type) + if (args.Type != LinuxInputEventType.KEY) + return; + + KeyboardKey key = InputUtilities.KeyFromKeyCode((LinuxKeyboardKeyCodes) args.Code); + bool isDown = args.Value != 0; + + //_logger.Verbose($"Keyboard Key: {(LinuxKeyboardKeyCodes)args.Code} | Down: {isDown}"); + + LinuxInputDevice.LinuxInputId identifier = keyboard.InputId; + OnIdentifierReceived(identifier, InputDeviceType.Keyboard); + ArtemisDevice? device = null; + + try { - case LinuxInputEventType.KEY: - KeyboardKey key = InputUtilities.KeyFromKeyCode((LinuxKeyboardKeyCodes) args.Code); - bool isDown = args.Value != 0; - - //_logger.Verbose($"Keyboard Key: {(LinuxKeyboardKeyCodes)args.Code} | Down: {isDown}"); - - LinuxInputDevice.LinuxInputId identifier = keyboard.InputId; - - OnIdentifierReceived(identifier, InputDeviceType.Keyboard); - - ArtemisDevice? device = null; - - try - { - device = _inputService.GetDeviceByIdentifier(this, identifier, InputDeviceType.Keyboard); - } - catch (Exception e) - { - _logger.Warning(e, "Failed to retrieve input device by its identifier"); - } - - OnKeyboardDataReceived(device, key, isDown); - break; + device = _inputService.GetDeviceByIdentifier(this, identifier, InputDeviceType.Keyboard); } + catch (Exception e) + { + _logger.Warning(e, "Failed to retrieve input device by its identifier"); + } + + OnKeyboardDataReceived(device, key, isDown); } private void HandleMouseData(LinuxInputDevice mouse, LinuxInputEventArgs args) { LinuxInputDevice.LinuxInputId identifier = mouse.InputId; - OnIdentifierReceived(identifier, InputDeviceType.Mouse); - ArtemisDevice? device = null; try @@ -112,13 +109,11 @@ public class LinuxInputProvider : InputProvider switch (args.Type) { case LinuxInputEventType.KEY: - var key = (LinuxKeyboardKeyCodes)args.Code; - if (key == LinuxKeyboardKeyCodes.BTN_TOUCH || + LinuxKeyboardKeyCodes key = (LinuxKeyboardKeyCodes) args.Code; + if (key == LinuxKeyboardKeyCodes.BTN_TOUCH || (key >= LinuxKeyboardKeyCodes.BTN_TOOL_PEN && key <= LinuxKeyboardKeyCodes.BTN_TOOL_QUADTAP)) - { //trackpad input, ignore. return; - } //0 - up //1 - down diff --git a/src/Artemis.UI.Shared/Services/NodeEditor/Commands/ConnectPins.cs b/src/Artemis.UI.Shared/Services/NodeEditor/Commands/ConnectPins.cs index c7b01be05..0b65a56e1 100644 --- a/src/Artemis.UI.Shared/Services/NodeEditor/Commands/ConnectPins.cs +++ b/src/Artemis.UI.Shared/Services/NodeEditor/Commands/ConnectPins.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Artemis.Core; namespace Artemis.UI.Shared.Services.NodeEditor.Commands; @@ -8,9 +9,9 @@ namespace Artemis.UI.Shared.Services.NodeEditor.Commands; /// public class ConnectPins : INodeEditorCommand { - private readonly List? _originalConnections; - private readonly IPin _source; - private readonly IPin _target; + private readonly IPin _output; + private readonly IPin _input; + private readonly IPin? _originalConnection; /// /// Creates a new instance of the class. @@ -19,10 +20,18 @@ public class ConnectPins : INodeEditorCommand /// The target of the connection. public ConnectPins(IPin source, IPin target) { - _source = source; - _target = target; + if (source.Direction == PinDirection.Output) + { + _output = source; + _input = target; + } + else + { + _output = target; + _input = source; + } - _originalConnections = _target.Direction == PinDirection.Input ? new List(_target.ConnectedTo) : null; + _originalConnection = _input.ConnectedTo.FirstOrDefault(); } #region Implementation of INodeEditorCommand @@ -33,20 +42,16 @@ public class ConnectPins : INodeEditorCommand /// public void Execute() { - if (_target.Direction == PinDirection.Input) - _target.DisconnectAll(); - _source.ConnectTo(_target); + _input.DisconnectAll(); + _output.ConnectTo(_input); } /// public void Undo() { - _target.DisconnectFrom(_source); - - if (_originalConnections == null) - return; - foreach (IPin pin in _originalConnections) - _target.ConnectTo(pin); + _input.DisconnectAll(); + if (_originalConnection != null) + _input.ConnectTo(_originalConnection); } #endregion diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/DataBinding/DataBindingViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/DataBinding/DataBindingViewModel.cs index 9000af7e3..eb1dd81e5 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/DataBinding/DataBindingViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/DataBinding/DataBindingViewModel.cs @@ -25,6 +25,7 @@ public class DataBindingViewModel : ActivatableViewModelBase private ObservableAsPropertyHelper? _layerProperty; private ObservableAsPropertyHelper? _nodeScriptViewModel; private bool _playing; + private bool _editorOpen; public DataBindingViewModel(IProfileEditorService profileEditorService, INodeVmFactory nodeVmFactory, IWindowService windowService, ISettingsService settingsService) { @@ -49,11 +50,18 @@ public class DataBindingViewModel : ActivatableViewModelBase .DisposeWith(d); _profileEditorService.Playing.CombineLatest(_profileEditorService.SuspendedEditing).Subscribe(tuple => _playing = tuple.First || tuple.Second).DisposeWith(d); - DispatcherTimer updateTimer = new(TimeSpan.FromMilliseconds(60.0 / 1000), DispatcherPriority.Render, Update); + DispatcherTimer updateTimer = new(TimeSpan.FromMilliseconds(25.0 / 1000), DispatcherPriority.Render, Update); + // TODO: Remove in favor of saving each time a node editor command is executed + DispatcherTimer saveTimer = new(TimeSpan.FromMinutes(2), DispatcherPriority.Normal, Save); + updateTimer.Start(); + saveTimer.Start(); + Disposable.Create(() => { updateTimer.Stop(); + saveTimer.Stop(); + _profileEditorService.SaveProfile(); }).DisposeWith(d); }); @@ -74,11 +82,19 @@ public class DataBindingViewModel : ActivatableViewModelBase public async Task OpenEditor() { - if (LayerProperty != null && LayerProperty.BaseDataBinding.IsEnabled) + if (LayerProperty == null || !LayerProperty.BaseDataBinding.IsEnabled) + return; + + try { + _editorOpen = true; await _windowService.ShowDialogAsync(("nodeScript", LayerProperty.BaseDataBinding.Script)); await _profileEditorService.SaveProfileAsync(); } + finally + { + _editorOpen = false; + } } private void Update(object? sender, EventArgs e) @@ -89,4 +105,10 @@ public class DataBindingViewModel : ActivatableViewModelBase LayerProperty?.UpdateDataBinding(); } + + private void Save(object? sender, EventArgs e) + { + if (!_editorOpen) + _profileEditorService.SaveProfile(); + } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowViewModel.cs b/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowViewModel.cs index c71578605..09dc7d049 100644 --- a/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowViewModel.cs +++ b/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowViewModel.cs @@ -12,6 +12,7 @@ using Artemis.UI.Shared; using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services.NodeEditor; using Artemis.UI.Shared.Services.NodeEditor.Commands; +using Artemis.UI.Shared.Services.ProfileEditor; using Avalonia; using Avalonia.Threading; using DynamicData; @@ -25,6 +26,7 @@ public class NodeScriptWindowViewModel : DialogViewModelBase private readonly INodeEditorService _nodeEditorService; private readonly INodeService _nodeService; private readonly ISettingsService _settingsService; + private readonly IProfileService _profileService; private readonly IWindowService _windowService; public NodeScriptWindowViewModel(NodeScript nodeScript, @@ -32,6 +34,7 @@ public class NodeScriptWindowViewModel : DialogViewModelBase INodeEditorService nodeEditorService, INodeVmFactory vmFactory, ISettingsService settingsService, + IProfileService profileService, IWindowService windowService) { NodeScript = nodeScript; @@ -43,6 +46,7 @@ public class NodeScriptWindowViewModel : DialogViewModelBase _nodeService = nodeService; _nodeEditorService = nodeEditorService; _settingsService = settingsService; + _profileService = profileService; _windowService = windowService; SourceList nodeSourceList = new(); @@ -60,8 +64,17 @@ public class NodeScriptWindowViewModel : DialogViewModelBase this.WhenActivated(d => { DispatcherTimer updateTimer = new(TimeSpan.FromMilliseconds(25.0 / 1000), DispatcherPriority.Normal, Update); + // TODO: Remove in favor of saving each time a node editor command is executed + DispatcherTimer saveTimer = new(TimeSpan.FromMinutes(2), DispatcherPriority.Normal, Save); + updateTimer.Start(); - Disposable.Create(() => updateTimer.Stop()).DisposeWith(d); + saveTimer.Start(); + + Disposable.Create(() => + { + updateTimer.Stop(); + saveTimer.Stop(); + }).DisposeWith(d); }); } @@ -133,4 +146,10 @@ public class NodeScriptWindowViewModel : DialogViewModelBase { NodeScript.Run(); } + + private void Save(object? sender, EventArgs e) + { + if (NodeScript.Context is Profile profile) + _profileService.SaveProfile(profile, true); + } } \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/DataModel/DataModelEventCycleNode.cs b/src/Artemis.VisualScripting/Nodes/DataModel/DataModelEventCycleNode.cs index cab064242..a5de87de0 100644 --- a/src/Artemis.VisualScripting/Nodes/DataModel/DataModelEventCycleNode.cs +++ b/src/Artemis.VisualScripting/Nodes/DataModel/DataModelEventCycleNode.cs @@ -48,9 +48,7 @@ public class DataModelEventCycleNode : Node propertyAccessor, OutputPin outputPin) in _propertyPins) + { + if (!outputPin.ConnectedTo.Any()) + continue; + object value = dataModelEvent.LastEventArgumentsUntyped != null ? propertyAccessor(dataModelEvent.LastEventArgumentsUntyped) : outputPin.Type.GetDefault()!; + outputPin.Value = outputPin.IsNumeric ? new Numeric(value) : value; + } + } + + private void UpdateDataModelPath() + { + DataModelPath? old = _dataModelPath; + _dataModelPath = Storage != null ? new DataModelPath(Storage) : null; + if (_dataModelPath != null) + _dataModelPath.PathValidated += DataModelPathOnPathValidated; + + if (old != null) + { + old.PathValidated -= DataModelPathOnPathValidated; + old.Dispose(); + } + + UpdateOutputPins(); + } + + private void UpdateOutputPins() + { + object? pathValue = _dataModelPath?.GetValue(); + if (pathValue is IDataModelEvent dataModelEvent) + CreatePins(dataModelEvent); + } + private void CreatePins(IDataModelEvent? dataModelEvent) { if (_dataModelEvent == dataModelEvent) @@ -72,34 +112,12 @@ public class DataModelEventNode : Node propertyAccessor, OutputPin outputPin) in _propertyPins) - { - if (!outputPin.ConnectedTo.Any()) - continue; - object value = dataModelEvent.LastEventArgumentsUntyped != null ? propertyAccessor(dataModelEvent.LastEventArgumentsUntyped) : outputPin.Type.GetDefault()!; - outputPin.Value = outputPin.IsNumeric ? new Numeric(value) : value; - } - } - - private void UpdateDataModelPath() - { - DataModelPath? old = _dataModelPath; - _dataModelPath = Storage != null ? new DataModelPath(Storage) : null; - old?.Dispose(); - - object? pathValue = _dataModelPath?.GetValue(); - if (pathValue is IDataModelEvent dataModelEvent) - CreatePins(dataModelEvent); + // Update the output pin now that the type is known and attempt to restore the connection that was likely missing + UpdateOutputPins(); + Script?.LoadConnections(); } /// diff --git a/src/Artemis.VisualScripting/Nodes/DataModel/DataModelNode.cs b/src/Artemis.VisualScripting/Nodes/DataModel/DataModelNode.cs index 411f7c1f1..3c3054e1e 100644 --- a/src/Artemis.VisualScripting/Nodes/DataModel/DataModelNode.cs +++ b/src/Artemis.VisualScripting/Nodes/DataModel/DataModelNode.cs @@ -1,7 +1,6 @@ using Artemis.Core; using Artemis.Storage.Entities.Profile; using Artemis.VisualScripting.Nodes.DataModel.Screens; -using Avalonia.Threading; namespace Artemis.VisualScripting.Nodes.DataModel; @@ -61,17 +60,24 @@ public class DataModelNode : Node