diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs index e37f18ffb..f2abc9aa3 100644 --- a/src/Artemis.Core/Constants.cs +++ b/src/Artemis.Core/Constants.cs @@ -91,11 +91,6 @@ public static class Constants /// public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), null); - /// - /// Gets the startup arguments provided to the application - /// - public static ReadOnlyCollection StartupArguments { get; set; } = null!; - internal static readonly CorePluginFeature CorePluginFeature = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Core")}; internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Effect Placeholder")}; @@ -153,6 +148,11 @@ public static class Constants typeof(decimal) }; + /// + /// Gets the startup arguments provided to the application + /// + public static ReadOnlyCollection StartupArguments { get; set; } = null!; + /// /// Gets the graphics context to be used for rendering by SkiaSharp. Can be set via /// . diff --git a/src/Artemis.Core/Events/Profiles/LayerPropertyKeyframeEventArgs.cs b/src/Artemis.Core/Events/Profiles/LayerPropertyKeyframeEventArgs.cs index 962240239..5a46b7c7f 100644 --- a/src/Artemis.Core/Events/Profiles/LayerPropertyKeyframeEventArgs.cs +++ b/src/Artemis.Core/Events/Profiles/LayerPropertyKeyframeEventArgs.cs @@ -1,20 +1,19 @@ using System; -namespace Artemis.Core -{ - /// - /// Provides data for layer property events. - /// - public class LayerPropertyKeyframeEventArgs : EventArgs - { - internal LayerPropertyKeyframeEventArgs(ILayerPropertyKeyframe keyframe) - { - Keyframe = keyframe; - } +namespace Artemis.Core; - /// - /// Gets the keyframe this event is related to - /// - public ILayerPropertyKeyframe Keyframe { get; } +/// +/// Provides data for layer property events. +/// +public class LayerPropertyKeyframeEventArgs : EventArgs +{ + internal LayerPropertyKeyframeEventArgs(ILayerPropertyKeyframe keyframe) + { + Keyframe = keyframe; } + + /// + /// Gets the keyframe this event is related to + /// + public ILayerPropertyKeyframe Keyframe { get; } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/BreakableModel.cs b/src/Artemis.Core/Models/BreakableModel.cs index 7276b6adb..1910cc2cc 100644 --- a/src/Artemis.Core/Models/BreakableModel.cs +++ b/src/Artemis.Core/Models/BreakableModel.cs @@ -69,11 +69,16 @@ public abstract class BreakableModel : CorePropertyChanged, IBreakableModel /// public void ClearBrokenState(string state) { - if (state == null) throw new ArgumentNullException(nameof(state)); + if (state == null) + throw new ArgumentNullException(nameof(state)); + + // If there was no broken state to begin with, done! if (BrokenState == null) return; + // Only clear similar broken states + if (BrokenState != state) + return; - if (BrokenState != state) return; BrokenState = null; BrokenStateException = null; OnBrokenStateChanged(); diff --git a/src/Artemis.Core/Services/Interfaces/ICoreService.cs b/src/Artemis.Core/Services/Interfaces/ICoreService.cs index 8dd297038..66de54365 100644 --- a/src/Artemis.Core/Services/Interfaces/ICoreService.cs +++ b/src/Artemis.Core/Services/Interfaces/ICoreService.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; namespace Artemis.Core.Services; @@ -27,7 +26,7 @@ public interface ICoreService : IArtemisService, IDisposable /// Gets or sets whether profiles are rendered each frame by calling their Render method /// bool ProfileRenderingDisabled { get; set; } - + /// /// Gets a boolean indicating whether Artemis is running in an elevated environment (admin permissions) /// diff --git a/src/Artemis.Core/Services/NodeService.cs b/src/Artemis.Core/Services/NodeService.cs index 2a9d7de6d..867fe8cb3 100644 --- a/src/Artemis.Core/Services/NodeService.cs +++ b/src/Artemis.Core/Services/NodeService.cs @@ -115,7 +115,7 @@ internal class NodeService : INodeService } } - node.Initialize(script); + node.TryInitialize(script); return node; } diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index c5eba9608..dc5d1e3b1 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -233,7 +233,7 @@ internal class PluginManagementService : IPluginManagementService } catch (Exception e) { - _logger.Warning(new ArtemisPluginException("Failed to load plugin", e), "Plugin exception"); + _logger.Warning(new ArtemisPluginException($"Failed to load plugin at {subDirectory}", e), "Plugin exception"); } } diff --git a/src/Artemis.Core/Services/ProcessMonitor/ProcessComparer.cs b/src/Artemis.Core/Services/ProcessMonitor/ProcessComparer.cs new file mode 100644 index 000000000..14ac5e765 --- /dev/null +++ b/src/Artemis.Core/Services/ProcessMonitor/ProcessComparer.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace Artemis.Core.Services; + +internal class ProcessComparer : IEqualityComparer +{ + public bool Equals(Process? x, Process? y) + { + if (x == null && y == null) return true; + if (x == null || y == null) return false; + return x.Id == y.Id && x.ProcessName == y.ProcessName && x.SessionId == y.SessionId; + } + + public int GetHashCode(Process? obj) + { + if (obj == null) return 0; + return obj.Id; + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/ProcessMonitor/ProcessMonitorService.cs b/src/Artemis.Core/Services/ProcessMonitor/ProcessMonitorService.cs index e5da142a7..fffcc100d 100644 --- a/src/Artemis.Core/Services/ProcessMonitor/ProcessMonitorService.cs +++ b/src/Artemis.Core/Services/ProcessMonitor/ProcessMonitorService.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using System.Linq; using System.Timers; using Artemis.Core.Modules; -using Serilog; namespace Artemis.Core.Services; @@ -42,20 +41,4 @@ internal class ProcessMonitorService : IProcessMonitorService { return _lastScannedProcesses; } -} - -internal class ProcessComparer : IEqualityComparer -{ - public bool Equals(Process? x, Process? y) - { - if (x == null && y == null) return true; - if (x == null || y == null) return false; - return x.Id == y.Id && x.ProcessName == y.ProcessName && x.SessionId == y.SessionId; - } - - public int GetHashCode(Process? obj) - { - if (obj == null) return 0; - return obj.Id; - } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/WebServer/WebServerService.cs b/src/Artemis.Core/Services/WebServer/WebServerService.cs index f082ed6df..83d269fc4 100644 --- a/src/Artemis.Core/Services/WebServer/WebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/WebServerService.cs @@ -38,6 +38,24 @@ internal class WebServerService : IWebServerService, IDisposable StartWebServer(); } + public event EventHandler? WebServerStopped; + public event EventHandler? WebServerStarted; + + protected virtual void OnWebServerStopped() + { + WebServerStopped?.Invoke(this, EventArgs.Empty); + } + + protected virtual void OnWebServerStarting() + { + WebServerStarting?.Invoke(this, EventArgs.Empty); + } + + protected virtual void OnWebServerStarted() + { + WebServerStarted?.Invoke(this, EventArgs.Empty); + } + private void WebServerEnabledSettingOnSettingChanged(object? sender, EventArgs e) { StartWebServer(); @@ -76,6 +94,7 @@ internal class WebServerService : IWebServerService, IDisposable public WebServer? Server { get; private set; } public PluginsModule PluginsModule { get; } + public event EventHandler? WebServerStarting; #region Web server managament @@ -129,7 +148,7 @@ internal class WebServerService : IWebServerService, IDisposable if (!_webServerEnabledSetting.Value) return; - + if (Constants.StartupArguments.Contains("--disable-webserver")) { _logger.Warning("Artemis launched with --disable-webserver, not enabling the webserver"); @@ -302,27 +321,4 @@ internal class WebServerService : IWebServerService, IDisposable } #endregion - - #region Events - - protected virtual void OnWebServerStopped() - { - WebServerStopped?.Invoke(this, EventArgs.Empty); - } - - protected virtual void OnWebServerStarting() - { - WebServerStarting?.Invoke(this, EventArgs.Empty); - } - - protected virtual void OnWebServerStarted() - { - WebServerStarted?.Invoke(this, EventArgs.Empty); - } - - public event EventHandler? WebServerStopped; - public event EventHandler? WebServerStarting; - public event EventHandler? WebServerStarted; - - #endregion } \ No newline at end of file diff --git a/src/Artemis.Core/Stores/NodeTypeStore.cs b/src/Artemis.Core/Stores/NodeTypeStore.cs index 401db6406..fb96f2794 100644 --- a/src/Artemis.Core/Stores/NodeTypeStore.cs +++ b/src/Artemis.Core/Stores/NodeTypeStore.cs @@ -62,9 +62,10 @@ internal class NodeTypeStore public static Plugin? GetPlugin(INode node) { + Type nodeType = node.GetType(); lock (Registrations) { - return Registrations.FirstOrDefault(r => r.Plugin.GetType().Assembly == node.GetType().Assembly)?.Plugin; + return Registrations.FirstOrDefault(r => r.NodeData.Type == nodeType)?.Plugin; } } diff --git a/src/Artemis.Core/Utilities/Numeric.cs b/src/Artemis.Core/Utilities/Numeric.cs index 36f04e14f..8fda4c66b 100644 --- a/src/Artemis.Core/Utilities/Numeric.cs +++ b/src/Artemis.Core/Utilities/Numeric.cs @@ -48,7 +48,7 @@ public readonly struct Numeric : IComparable, IConvertible { _value = value; } - + /// /// Creates a new instance of from a /// @@ -160,10 +160,25 @@ public readonly struct Numeric : IComparable, IConvertible return (byte) Math.Clamp(p._value, 0, 255); } - public static implicit operator Numeric(double d) => new(d); - public static implicit operator Numeric(float f) => new(f); - public static implicit operator Numeric(int i) => new(i); - public static implicit operator Numeric(byte b) => new(b); + public static implicit operator Numeric(double d) + { + return new Numeric(d); + } + + public static implicit operator Numeric(float f) + { + return new Numeric(f); + } + + public static implicit operator Numeric(int i) + { + return new Numeric(i); + } + + public static implicit operator Numeric(byte b) + { + return new Numeric(b); + } public static implicit operator long(Numeric p) { diff --git a/src/Artemis.Core/VisualScripting/InputPin.cs b/src/Artemis.Core/VisualScripting/InputPin.cs index 9db82c693..90933f31b 100644 --- a/src/Artemis.Core/VisualScripting/InputPin.cs +++ b/src/Artemis.Core/VisualScripting/InputPin.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; using Newtonsoft.Json; namespace Artemis.Core; @@ -96,7 +94,7 @@ public sealed class InputPin : Pin { if (type == _type) return; - + base.ChangeType(type, ref _type); Value = type.GetDefault(); } @@ -111,9 +109,13 @@ public sealed class InputPin : Pin Value = Type.GetDefault()!; } else if (ConnectedTo.Count > 0) + { Value = ConnectedTo[0].PinValue; + } else + { Value = null; + } } #endregion diff --git a/src/Artemis.Core/VisualScripting/Interfaces/INode.cs b/src/Artemis.Core/VisualScripting/Interfaces/INode.cs index b5e16a50b..022cddfaa 100644 --- a/src/Artemis.Core/VisualScripting/Interfaces/INode.cs +++ b/src/Artemis.Core/VisualScripting/Interfaces/INode.cs @@ -8,7 +8,7 @@ namespace Artemis.Core; /// /// Represents a kind of node inside a /// -public interface INode : INotifyPropertyChanged +public interface INode : INotifyPropertyChanged, IBreakableModel { /// /// Gets or sets the ID of the node. @@ -59,7 +59,7 @@ public interface INode : INotifyPropertyChanged /// Called when the node resets /// event EventHandler Resetting; - + /// /// Occurs when a pin was added to the node /// @@ -69,7 +69,7 @@ public interface INode : INotifyPropertyChanged /// Occurs when a pin was removed from the node /// event EventHandler> PinRemoved; - + /// /// Occurs when a pin collection was added to the node /// @@ -81,15 +81,15 @@ public interface INode : INotifyPropertyChanged event EventHandler> PinCollectionRemoved; /// - /// Called when the node was loaded from storage or newly created + /// Attempts to initialize the node. /// /// The script the node is contained in - void Initialize(INodeScript script); + void TryInitialize(INodeScript script); /// - /// Evaluates the value of the output pins of this node + /// Attempts to evaluate the value of the output pins of this node /// - void Evaluate(); + void TryEvaluate(); /// /// Resets the node causing all pins to re-evaluate the next time is called diff --git a/src/Artemis.Core/VisualScripting/Interfaces/IPin.cs b/src/Artemis.Core/VisualScripting/Interfaces/IPin.cs index 67a0619c3..be3f1868e 100644 --- a/src/Artemis.Core/VisualScripting/Interfaces/IPin.cs +++ b/src/Artemis.Core/VisualScripting/Interfaces/IPin.cs @@ -85,7 +85,10 @@ public interface IPin /// Determines whether this pin is compatible with the given type /// /// The type to check for compatibility - /// A boolean indicating whether or not enums should be exactly equal or just both be enums + /// + /// A boolean indicating whether or not enums should be exactly equal or just both be + /// enums + /// /// if the type is compatible, otherwise . public bool IsTypeCompatible(Type type, bool forgivingEnumMatching = true); } \ No newline at end of file diff --git a/src/Artemis.Core/VisualScripting/Internal/DataBindingExitNode.cs b/src/Artemis.Core/VisualScripting/Internal/DataBindingExitNode.cs index cd7818b41..6b4d367f5 100644 --- a/src/Artemis.Core/VisualScripting/Internal/DataBindingExitNode.cs +++ b/src/Artemis.Core/VisualScripting/Internal/DataBindingExitNode.cs @@ -26,6 +26,17 @@ internal class DataBindingExitNode : Node, IExitNode property.SetValue(pendingValue); } + public override void Evaluate() + { + foreach ((IDataBindingProperty? property, InputPin? inputPin) in _propertyPins) + { + if (inputPin.ConnectedTo.Any()) + _propertyValues[property] = inputPin.Value!; + else + _propertyValues.Remove(property); + } + } + private void ClearInputPins() { while (Pins.Any()) @@ -59,15 +70,4 @@ internal class DataBindingExitNode : Node, IExitNode } public override bool IsExitNode => true; - - public override void Evaluate() - { - foreach ((IDataBindingProperty? property, InputPin? inputPin) in _propertyPins) - { - if (inputPin.ConnectedTo.Any()) - _propertyValues[property] = inputPin.Value!; - else - _propertyValues.Remove(property); - } - } } \ No newline at end of file diff --git a/src/Artemis.Core/VisualScripting/Node.cs b/src/Artemis.Core/VisualScripting/Node.cs index 5c1ccb03f..7e2161580 100644 --- a/src/Artemis.Core/VisualScripting/Node.cs +++ b/src/Artemis.Core/VisualScripting/Node.cs @@ -12,7 +12,7 @@ namespace Artemis.Core; /// /// Represents a kind of node inside a /// -public abstract class Node : CorePropertyChanged, INode +public abstract class Node : BreakableModel, INode { /// public event EventHandler? Resetting; @@ -95,6 +95,9 @@ public abstract class Node : CorePropertyChanged, INode /// public IReadOnlyCollection PinCollections => new ReadOnlyCollection(_pinCollections); + /// + public override string BrokenDisplayName => Name; + #endregion #region Construtors @@ -335,12 +338,17 @@ public abstract class Node : CorePropertyChanged, INode return isRemoved; } - /// + /// + /// Called when the node was loaded from storage or newly created + /// + /// The script the node is contained in public virtual void Initialize(INodeScript script) { } - /// + /// + /// Evaluates the value of the output pins of this node + /// public abstract void Evaluate(); /// @@ -354,6 +362,18 @@ public abstract class Node : CorePropertyChanged, INode Resetting?.Invoke(this, EventArgs.Empty); } + /// + public void TryInitialize(INodeScript script) + { + TryOrBreak(() => Initialize(script), "Failed to initialize"); + } + + /// + public void TryEvaluate() + { + TryOrBreak(Evaluate, "Failed to evaluate"); + } + /// /// Called whenever the node must show it's custom view model, if , no custom view model is used /// diff --git a/src/Artemis.Core/VisualScripting/NodeScript.cs b/src/Artemis.Core/VisualScripting/NodeScript.cs index 8abc29fbd..1c77626f8 100644 --- a/src/Artemis.Core/VisualScripting/NodeScript.cs +++ b/src/Artemis.Core/VisualScripting/NodeScript.cs @@ -14,13 +14,19 @@ namespace Artemis.Core; /// public abstract class NodeScript : CorePropertyChanged, INodeScript { - private void NodeTypeStoreOnNodeTypeChanged(object? sender, NodeTypeStoreEvent e) + private void NodeTypeStoreOnNodeTypeAdded(object? sender, NodeTypeStoreEvent e) { - // Only respond to node changes applicable to the current script if (Entity.Nodes.Any(n => e.TypeRegistration.MatchesEntity(n))) Load(); } + private void NodeTypeStoreOnNodeTypeRemoved(object? sender, NodeTypeStoreEvent e) + { + List nodes = Nodes.Where(n => n.GetType() == e.TypeRegistration.NodeData.Type).ToList(); + foreach (INode node in nodes) + RemoveNode(node); + } + /// public event EventHandler>? NodeAdded; @@ -79,8 +85,8 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript Entity = new NodeScriptEntity(); ExitNode = null!; - NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeChanged; - NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeChanged; + NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeAdded; + NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeRemoved; } internal NodeScript(string name, string description, NodeScriptEntity entity, object? context = null) @@ -91,8 +97,8 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript Context = context; ExitNode = null!; - NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeChanged; - NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeChanged; + NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeAdded; + NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeRemoved; } #endregion @@ -108,7 +114,7 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript node.Reset(); } - ExitNode.Evaluate(); + ExitNode.TryEvaluate(); } /// @@ -136,8 +142,8 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript /// public void Dispose() { - NodeTypeStore.NodeTypeAdded -= NodeTypeStoreOnNodeTypeChanged; - NodeTypeStore.NodeTypeRemoved -= NodeTypeStoreOnNodeTypeChanged; + NodeTypeStore.NodeTypeAdded -= NodeTypeStoreOnNodeTypeAdded; + NodeTypeStore.NodeTypeRemoved -= NodeTypeStoreOnNodeTypeRemoved; lock (_nodes) { @@ -346,7 +352,7 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript sourcePinId++; continue; } - + foreach (IPin targetPin in sourcePin.ConnectedTo) { int targetPinCollectionId = -1; diff --git a/src/Artemis.Core/VisualScripting/OutputPin.cs b/src/Artemis.Core/VisualScripting/OutputPin.cs index 2e135eb13..52bd0993a 100644 --- a/src/Artemis.Core/VisualScripting/OutputPin.cs +++ b/src/Artemis.Core/VisualScripting/OutputPin.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; using Newtonsoft.Json; namespace Artemis.Core; @@ -43,7 +41,7 @@ public sealed class OutputPin : Pin get { if (!IsEvaluated) - Node?.Evaluate(); + Node?.TryEvaluate(); return _value; } @@ -115,7 +113,7 @@ public sealed class OutputPin : Pin get { if (!IsEvaluated) - Node?.Evaluate(); + Node?.TryEvaluate(); return _value; } diff --git a/src/Artemis.Core/VisualScripting/Pin.cs b/src/Artemis.Core/VisualScripting/Pin.cs index a562f7961..742e0e4b7 100644 --- a/src/Artemis.Core/VisualScripting/Pin.cs +++ b/src/Artemis.Core/VisualScripting/Pin.cs @@ -143,30 +143,20 @@ public abstract class Pin : CorePropertyChanged, IPin /// The backing field of the current type of the pin. protected void ChangeType(Type type, ref Type currentType) { - // Enums are a special case that disconnect and, if still compatible, reconnect - if (type.IsEnum && currentType.IsEnum) - { - List connections = new(ConnectedTo); - DisconnectAll(); + if (currentType == type) + return; - // Change the type - SetAndNotify(ref currentType, type, nameof(Type)); - IsNumeric = type == typeof(Numeric); + bool changingEnums = type.IsEnum && currentType.IsEnum; - foreach (IPin pin in connections.Where(p => p.IsTypeCompatible(type))) - ConnectTo(pin); - } - // Disconnect pins incompatible with the new type - else - { - List toDisconnect = ConnectedTo.Where(p => !p.IsTypeCompatible(type, false)).ToList(); - foreach (IPin pin in toDisconnect) - DisconnectFrom(pin); + List connections = new(ConnectedTo); + DisconnectAll(); - // Change the type - SetAndNotify(ref currentType, type, nameof(Type)); - IsNumeric = type == typeof(Numeric); - } + // Change the type + SetAndNotify(ref currentType, type, nameof(Type)); + IsNumeric = type == typeof(Numeric); + + foreach (IPin pin in connections.Where(p => p.IsTypeCompatible(type, changingEnums))) + ConnectTo(pin); } #endregion diff --git a/src/Artemis.UI.Linux/Providers/Input/LinuxInputProvider.cs b/src/Artemis.UI.Linux/Providers/Input/LinuxInputProvider.cs index 26c5c36ed..e735a8ed9 100644 --- a/src/Artemis.UI.Linux/Providers/Input/LinuxInputProvider.cs +++ b/src/Artemis.UI.Linux/Providers/Input/LinuxInputProvider.cs @@ -33,14 +33,12 @@ 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); } @@ -51,7 +49,7 @@ public class LinuxInputProvider : InputProvider { if (sender is not LinuxInputDeviceReader reader) return; - + switch (reader.InputDevice.DeviceType) { case LinuxDeviceType.Keyboard: @@ -69,7 +67,7 @@ public class LinuxInputProvider : InputProvider { if (args.Type != LinuxInputEventType.KEY) return; - + KeyboardKey key = InputUtilities.KeyFromKeyCode((LinuxKeyboardKeyCodes) args.Code); bool isDown = args.Value != 0; diff --git a/src/Artemis.UI.Shared/Behaviors/LostFocusNumericUpDownBindingBehavior.cs b/src/Artemis.UI.Shared/Behaviors/LostFocusNumericUpDownBindingBehavior.cs index 3c73456a7..23e2bdbdb 100644 --- a/src/Artemis.UI.Shared/Behaviors/LostFocusNumericUpDownBindingBehavior.cs +++ b/src/Artemis.UI.Shared/Behaviors/LostFocusNumericUpDownBindingBehavior.cs @@ -4,7 +4,6 @@ using Avalonia.Controls; using Avalonia.Data; using Avalonia.Interactivity; using Avalonia.Xaml.Interactivity; -using FluentAvalonia.UI.Controls; namespace Artemis.UI.Shared.Behaviors; diff --git a/src/Artemis.UI.Shared/Services/MainWindow/IMainWindowService.cs b/src/Artemis.UI.Shared/Services/MainWindow/IMainWindowService.cs index c19b06c78..bf7716f0b 100644 --- a/src/Artemis.UI.Shared/Services/MainWindow/IMainWindowService.cs +++ b/src/Artemis.UI.Shared/Services/MainWindow/IMainWindowService.cs @@ -19,12 +19,12 @@ public interface IMainWindowService : IArtemisSharedUIService void ConfigureMainWindowProvider(IMainWindowProvider mainWindowProvider); /// - /// Opens the main window if it is not already open + /// Opens the main window if it is not already open, must be called on the UI thread /// void OpenMainWindow(); /// - /// Closes the main window if it is not already closed + /// Closes the main window if it is not already closed, must be called on the UI thread /// void CloseMainWindow(); @@ -37,7 +37,7 @@ public interface IMainWindowService : IArtemisSharedUIService /// Occurs when the main window has been closed /// public event EventHandler? MainWindowClosed; - + /// /// Occurs when the main window has been focused /// diff --git a/src/Artemis.UI.Shared/Services/MainWindow/MainWindowService.cs b/src/Artemis.UI.Shared/Services/MainWindow/MainWindowService.cs index 407bec98b..fbdda52e1 100644 --- a/src/Artemis.UI.Shared/Services/MainWindow/MainWindowService.cs +++ b/src/Artemis.UI.Shared/Services/MainWindow/MainWindowService.cs @@ -15,7 +15,7 @@ internal class MainWindowService : IMainWindowService { MainWindowClosed?.Invoke(this, EventArgs.Empty); } - + protected virtual void OnMainWindowFocused() { MainWindowFocused?.Invoke(this, EventArgs.Empty); @@ -53,7 +53,7 @@ internal class MainWindowService : IMainWindowService { SyncWithManager(); } - + private void HandleMainWindowFocused(object? sender, EventArgs e) { OnMainWindowFocused(); @@ -83,7 +83,7 @@ internal class MainWindowService : IMainWindowService _mainWindowManager.MainWindowClosed += HandleMainWindowClosed; _mainWindowManager.MainWindowFocused += HandleMainWindowFocused; _mainWindowManager.MainWindowUnfocused += HandleMainWindowUnfocused; - + // Sync up with the new manager's state SyncWithManager(); } diff --git a/src/Artemis.UI.Shared/Services/NodeEditor/Commands/ConnectPins.cs b/src/Artemis.UI.Shared/Services/NodeEditor/Commands/ConnectPins.cs index 0b65a56e1..1898745b1 100644 --- a/src/Artemis.UI.Shared/Services/NodeEditor/Commands/ConnectPins.cs +++ b/src/Artemis.UI.Shared/Services/NodeEditor/Commands/ConnectPins.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Linq; +using System.Linq; using Artemis.Core; namespace Artemis.UI.Shared.Services.NodeEditor.Commands; @@ -9,9 +8,9 @@ namespace Artemis.UI.Shared.Services.NodeEditor.Commands; /// public class ConnectPins : INodeEditorCommand { - private readonly IPin _output; private readonly IPin _input; private readonly IPin? _originalConnection; + private readonly IPin _output; /// /// Creates a new instance of the class. diff --git a/src/Artemis.UI.Shared/Services/NodeEditor/NodeConnectionStore.cs b/src/Artemis.UI.Shared/Services/NodeEditor/NodeConnectionStore.cs index 596e66304..8182aec2b 100644 --- a/src/Artemis.UI.Shared/Services/NodeEditor/NodeConnectionStore.cs +++ b/src/Artemis.UI.Shared/Services/NodeEditor/NodeConnectionStore.cs @@ -31,7 +31,7 @@ public class NodeConnectionStore public void Store() { _pinConnections.Clear(); - + // Iterate to save foreach (IPin nodePin in Node.Pins.ToList()) _pinConnections.Add(nodePin, new List(nodePin.ConnectedTo)); diff --git a/src/Artemis.UI.Windows/App.axaml.cs b/src/Artemis.UI.Windows/App.axaml.cs index f82d58f29..c41c00aa1 100644 --- a/src/Artemis.UI.Windows/App.axaml.cs +++ b/src/Artemis.UI.Windows/App.axaml.cs @@ -24,11 +24,6 @@ public class App : Application private StandardKernel? _kernel; private bool _shutDown; - // ReSharper disable NotAccessedField.Local - private ApplicationStateManager? _applicationStateManager; - private Mutex? _artemisMutex; - // ReSharper restore NotAccessedField.Local - public override void Initialize() { // If Artemis is already running, bring it to foreground and stop this process @@ -110,4 +105,10 @@ public class App : Application } } } + + // ReSharper disable NotAccessedField.Local + private ApplicationStateManager? _applicationStateManager; + + private Mutex? _artemisMutex; + // ReSharper restore NotAccessedField.Local } \ No newline at end of file diff --git a/src/Artemis.UI.Windows/Providers/Input/WindowsInputProvider.cs b/src/Artemis.UI.Windows/Providers/Input/WindowsInputProvider.cs index 605e23892..d834c3002 100644 --- a/src/Artemis.UI.Windows/Providers/Input/WindowsInputProvider.cs +++ b/src/Artemis.UI.Windows/Providers/Input/WindowsInputProvider.cs @@ -17,10 +17,10 @@ public class WindowsInputProvider : InputProvider private readonly IInputService _inputService; private readonly ILogger _logger; + private readonly SpongeWindow _sponge; private readonly Timer _taskManagerTimer; private DateTime _lastMouseUpdate; private int _lastProcessId; - private readonly SpongeWindow _sponge; public WindowsInputProvider(ILogger logger, IInputService inputService) { diff --git a/src/Artemis.UI.Windows/Providers/UpdateProvider.cs b/src/Artemis.UI.Windows/Providers/UpdateProvider.cs index 788c02231..40ffbc776 100644 --- a/src/Artemis.UI.Windows/Providers/UpdateProvider.cs +++ b/src/Artemis.UI.Windows/Providers/UpdateProvider.cs @@ -24,8 +24,8 @@ namespace Artemis.UI.Windows.Providers; public class UpdateProvider : IUpdateProvider, IDisposable { - private const string ApiUrl = "https://dev.azure.com/artemis-rgb/Artemis/_apis/"; - private const string InstallerUrl = "https://builds.artemis-rgb.com/binaries/Artemis.Installer.exe"; + private const string API_URL = "https://dev.azure.com/artemis-rgb/Artemis/_apis/"; + private const string INSTALLER_URL = "https://builds.artemis-rgb.com/binaries/Artemis.Installer.exe"; private readonly ILogger _logger; private readonly IMainWindowService _mainWindowService; @@ -42,7 +42,7 @@ public class UpdateProvider : IUpdateProvider, IDisposable public async Task GetBuildInfo(int buildDefinition, string? buildNumber = null) { - Url request = ApiUrl.AppendPathSegments("build", "builds") + Url request = API_URL.AppendPathSegments("build", "builds") .SetQueryParam("definitions", buildDefinition) .SetQueryParam("resultFilter", "succeeded") .SetQueryParam("$top", 1) @@ -143,9 +143,9 @@ public class UpdateProvider : IUpdateProvider, IDisposable string installerDirectory = Path.Combine(Constants.DataFolder, "installer"); string installerPath = Path.Combine(installerDirectory, "Artemis.Installer.exe"); - _logger.Information("UpdateInstaller: Downloading installer from {DownloadUrl}", InstallerUrl); + _logger.Information("UpdateInstaller: Downloading installer from {DownloadUrl}", INSTALLER_URL); using HttpClient client = new(); - HttpResponseMessage httpResponseMessage = await client.GetAsync(InstallerUrl); + HttpResponseMessage httpResponseMessage = await client.GetAsync(INSTALLER_URL); if (!httpResponseMessage.IsSuccessStatusCode) throw new ArtemisUIException($"Failed to download installer, status code {httpResponseMessage.StatusCode}"); diff --git a/src/Artemis.UI/ArtemisBootstrapper.cs b/src/Artemis.UI/ArtemisBootstrapper.cs index 7bade4f67..901388bf8 100644 --- a/src/Artemis.UI/ArtemisBootstrapper.cs +++ b/src/Artemis.UI/ArtemisBootstrapper.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; using System.Reactive; using Artemis.Core; using Artemis.Core.Ninject; diff --git a/src/Artemis.UI/Controllers/RemoteController.cs b/src/Artemis.UI/Controllers/RemoteController.cs index 15db2f0ef..56b88ef6a 100644 --- a/src/Artemis.UI/Controllers/RemoteController.cs +++ b/src/Artemis.UI/Controllers/RemoteController.cs @@ -2,6 +2,7 @@ using System; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Shared.Services.MainWindow; +using Avalonia.Threading; using EmbedIO; using EmbedIO.Routing; using EmbedIO.WebApi; @@ -22,7 +23,7 @@ public class RemoteController : WebApiController [Route(HttpVerbs.Post, "/remote/bring-to-foreground")] public void PostBringToForeground() { - _mainWindowService.OpenMainWindow(); + Dispatcher.UIThread.Post(() => _mainWindowService.OpenMainWindow()); } [Route(HttpVerbs.Post, "/remote/restart")] diff --git a/src/Artemis.UI/MainWindow.axaml.cs b/src/Artemis.UI/MainWindow.axaml.cs index a22f46831..14732e730 100644 --- a/src/Artemis.UI/MainWindow.axaml.cs +++ b/src/Artemis.UI/MainWindow.axaml.cs @@ -44,7 +44,7 @@ public class MainWindow : ReactiveCoreWindow SetTitleBar(this.Get("DragHandle")); } } - + private void OnActivated(object? sender, EventArgs e) { ViewModel?.Focused(); diff --git a/src/Artemis.UI/Screens/Device/DevicePropertiesViewModel.cs b/src/Artemis.UI/Screens/Device/DevicePropertiesViewModel.cs index 98ea28983..d0200cb70 100644 --- a/src/Artemis.UI/Screens/Device/DevicePropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/Device/DevicePropertiesViewModel.cs @@ -37,6 +37,16 @@ public class DevicePropertiesViewModel : DialogViewModelBase ClearSelectedLeds = ReactiveCommand.Create(ExecuteClearSelectedLeds); } + public ArtemisDevice Device + { + get => _device; + set => RaiseAndSetIfChanged(ref _device, value); + } + + public ObservableCollection SelectedLeds { get; } + public ObservableCollection Tabs { get; } + public ReactiveCommand ClearSelectedLeds { get; } + private void RgbServiceOnDeviceAdded(object? sender, DeviceEventArgs e) { if (e.Device.Identifier != Device.Identifier || Device == e.Device) @@ -52,16 +62,6 @@ public class DevicePropertiesViewModel : DialogViewModelBase SelectedLeds.Clear(); } - public ArtemisDevice Device - { - get => _device; - set => RaiseAndSetIfChanged(ref _device, value); - } - - public ObservableCollection SelectedLeds { get; } - public ObservableCollection Tabs { get; } - public ReactiveCommand ClearSelectedLeds { get; } - private void AddTabs() { Tabs.Add(_deviceVmFactory.DevicePropertiesTabViewModel(Device)); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/Dialogs/LayerHintsDialogViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/Dialogs/LayerHintsDialogViewModel.cs index de7375ccb..baac1b81f 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/Dialogs/LayerHintsDialogViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/Dialogs/LayerHintsDialogViewModel.cs @@ -32,7 +32,7 @@ public class LayerHintsDialogViewModel : DialogViewModelBase .Subscribe(c => AdaptionHints.Add(CreateHintViewModel(c.EventArgs.AdaptionHint))) .DisposeWith(d); Observable.FromEventPattern(x => layer.Adapter.AdapterHintRemoved += x, x => layer.Adapter.AdapterHintRemoved -= x) - .Subscribe(c => AdaptionHints.Remove(AdaptionHints.FirstOrDefault(h => h.AdaptionHint == c.EventArgs.AdaptionHint)!)) + .Subscribe(c => AdaptionHints.RemoveMany(AdaptionHints.Where(h => h.AdaptionHint == c.EventArgs.AdaptionHint))) .DisposeWith(d); AdaptionHints.AddRange(Layer.Adapter.AdaptionHints.Select(CreateHintViewModel)); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml.cs index 960db3e86..d519466b3 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml.cs @@ -15,10 +15,10 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree; public class ProfileTreeView : ReactiveUserControl { + private readonly TreeView _treeView; private Image? _dragAdorner; private Point _dragStartPosition; private Point _elementDragOffset; - private readonly TreeView _treeView; public ProfileTreeView() { diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs index 6ccaea894..95e9b7f01 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs @@ -293,6 +293,7 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase CanPaste = false; return; } + CanPaste = formats.Contains(ProfileElementExtensions.ClipboardDataFormat); } 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 eb1dd81e5..cb155b447 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/DataBinding/DataBindingViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/DataBinding/DataBindingViewModel.cs @@ -22,10 +22,10 @@ public class DataBindingViewModel : ActivatableViewModelBase private readonly IProfileEditorService _profileEditorService; private readonly IWindowService _windowService; private ObservableAsPropertyHelper? _dataBindingEnabled; + private bool _editorOpen; private ObservableAsPropertyHelper? _layerProperty; private ObservableAsPropertyHelper? _nodeScriptViewModel; private bool _playing; - private bool _editorOpen; public DataBindingViewModel(IProfileEditorService profileEditorService, INodeVmFactory nodeVmFactory, IWindowService windowService, ISettingsService settingsService) { diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelinePropertyViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelinePropertyViewModel.cs index 0b754b23b..211c91130 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelinePropertyViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelinePropertyViewModel.cs @@ -42,7 +42,7 @@ public class TimelinePropertyViewModel : ActivatableViewModelBase, ITimelineP .Subscribe(e => _keyframes.Add((LayerPropertyKeyframe) e.EventArgs.Keyframe)) .DisposeWith(d); Observable.FromEventPattern(x => LayerProperty.KeyframeRemoved += x, x => LayerProperty.KeyframeRemoved -= x) - .Subscribe(e => _keyframes.Remove((LayerPropertyKeyframe) e.EventArgs.Keyframe)) + .Subscribe(e => _keyframes.RemoveMany(_keyframes.Items.Where(k => k == e.EventArgs.Keyframe))) .DisposeWith(d); _keyframes.Edit(k => diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs index 24a29ad59..435ab4655 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs @@ -22,9 +22,9 @@ namespace Artemis.UI.Screens.ProfileEditor; public class ProfileEditorViewModel : MainScreenViewModel { + private readonly IMainWindowService _mainWindowService; private readonly IProfileEditorService _profileEditorService; private readonly ISettingsService _settingsService; - private readonly IMainWindowService _mainWindowService; private readonly SourceList _tools; private DisplayConditionScriptViewModel? _displayConditionScriptViewModel; private ObservableAsPropertyHelper? _history; @@ -69,7 +69,7 @@ public class ProfileEditorViewModel : MainScreenViewModel _profileConfiguration = profileEditorService.ProfileConfiguration.ToProperty(this, vm => vm.ProfileConfiguration).DisposeWith(d); _history = profileEditorService.History.ToProperty(this, vm => vm.History).DisposeWith(d); _suspendedEditing = profileEditorService.SuspendedEditing.ToProperty(this, vm => vm.SuspendedEditing).DisposeWith(d); - + mainWindowService.MainWindowFocused += MainWindowServiceOnMainWindowFocused; mainWindowService.MainWindowUnfocused += MainWindowServiceOnMainWindowUnfocused; @@ -78,7 +78,7 @@ public class ProfileEditorViewModel : MainScreenViewModel mainWindowService.MainWindowFocused -= MainWindowServiceOnMainWindowFocused; mainWindowService.MainWindowUnfocused -= MainWindowServiceOnMainWindowUnfocused; }).DisposeWith(d); - + // Slow and steady wins the race (and doesn't lock up the entire UI) Dispatcher.UIThread.Post(() => StatusBarViewModel = statusBarViewModel, DispatcherPriority.Loaded); Dispatcher.UIThread.Post(() => VisualEditorViewModel = visualEditorViewModel, DispatcherPriority.Loaded); @@ -165,7 +165,7 @@ public class ProfileEditorViewModel : MainScreenViewModel toolViewModel.IsSelected = false; }); } - + private void MainWindowServiceOnMainWindowFocused(object? sender, EventArgs e) { if (_settingsService.GetSetting("ProfileEditor.AutoSuspend", true).Value) diff --git a/src/Artemis.UI/Screens/Root/RootViewModel.cs b/src/Artemis.UI/Screens/Root/RootViewModel.cs index 61fbe58c2..f34d77f50 100644 --- a/src/Artemis.UI/Screens/Root/RootViewModel.cs +++ b/src/Artemis.UI/Screens/Root/RootViewModel.cs @@ -54,7 +54,7 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi _defaultTitleBarViewModel = defaultTitleBarViewModel; _sidebarVmFactory = sidebarVmFactory; _lifeTime = (IClassicDesktopStyleApplicationLifetime) Application.Current!.ApplicationLifetime!; - + mainWindowService.ConfigureMainWindowProvider(this); DisplayAccordingToSettings(); @@ -162,20 +162,17 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi /// public void OpenMainWindow() { - Dispatcher.UIThread.Post(() => + if (_lifeTime.MainWindow == null) { - if (_lifeTime.MainWindow == null) - { - SidebarViewModel = _sidebarVmFactory.SidebarViewModel(this); - _lifeTime.MainWindow = new MainWindow {DataContext = this}; - _lifeTime.MainWindow.Show(); - _lifeTime.MainWindow.Closing += CurrentMainWindowOnClosing; - } + SidebarViewModel = _sidebarVmFactory.SidebarViewModel(this); + _lifeTime.MainWindow = new MainWindow {DataContext = this}; + _lifeTime.MainWindow.Show(); + _lifeTime.MainWindow.Closing += CurrentMainWindowOnClosing; + } - _lifeTime.MainWindow.WindowState = WindowState.Normal; - _lifeTime.MainWindow.Activate(); - OnMainWindowOpened(); - }); + _lifeTime.MainWindow.WindowState = WindowState.Normal; + _lifeTime.MainWindow.Activate(); + OnMainWindowOpened(); } /// @@ -183,7 +180,7 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi { Dispatcher.UIThread.Post(() => { _lifeTime.MainWindow?.Close(); }); } - + public void Focused() { IsMainWindowFocused = true; @@ -201,7 +198,7 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi /// public event EventHandler? MainWindowClosed; - + /// public event EventHandler? MainWindowFocused; @@ -217,7 +214,7 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi { MainWindowClosed?.Invoke(this, EventArgs.Empty); } - + protected virtual void OnMainWindowFocused() { MainWindowFocused?.Invoke(this, EventArgs.Empty); diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs b/src/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs index 61278c700..36f373bda 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs +++ b/src/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs @@ -64,7 +64,7 @@ public class SidebarCategoryViewModel : ActivatableViewModelBase .Subscribe(e => profileConfigurations.Add(e.EventArgs.ProfileConfiguration)) .DisposeWith(d); Observable.FromEventPattern(x => profileCategory.ProfileConfigurationRemoved += x, x => profileCategory.ProfileConfigurationRemoved -= x) - .Subscribe(e => profileConfigurations.Remove(e.EventArgs.ProfileConfiguration)) + .Subscribe(e => profileConfigurations.RemoveMany(profileConfigurations.Items.Where(c => c == e.EventArgs.ProfileConfiguration))) .DisposeWith(d); profileEditorService.ProfileConfiguration.Subscribe(p => SelectedProfileConfiguration = ProfileConfigurations.FirstOrDefault(c => ReferenceEquals(c.ProfileConfiguration, p))) diff --git a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs index c642578a5..dd0f738dd 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs +++ b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs @@ -21,8 +21,8 @@ public class SurfaceEditorViewModel : MainScreenViewModel private readonly IDeviceService _deviceService; private readonly IDeviceVmFactory _deviceVmFactory; private readonly IRgbService _rgbService; - private readonly ISurfaceVmFactory _surfaceVmFactory; private readonly ISettingsService _settingsService; + private readonly ISurfaceVmFactory _surfaceVmFactory; private readonly IWindowService _windowService; private bool _colorDevices; private bool _colorFirstLedOnly; @@ -72,27 +72,6 @@ public class SurfaceEditorViewModel : MainScreenViewModel }); } - private void RgbServiceOnDeviceAdded(object? sender, DeviceEventArgs e) - { - if (!e.Device.IsEnabled) - return; - - SurfaceDeviceViewModels.Add(_surfaceVmFactory.SurfaceDeviceViewModel(e.Device, this)); - ListDeviceViewModels.Add(_surfaceVmFactory.ListDeviceViewModel(e.Device, this)); - SurfaceDeviceViewModels.Sort(l => l.Device.ZIndex * -1); - ListDeviceViewModels.Sort(l => l.Device.ZIndex * -1); - } - - private void RgbServiceOnDeviceRemoved(object? sender, DeviceEventArgs e) - { - SurfaceDeviceViewModel? surfaceVm = SurfaceDeviceViewModels.FirstOrDefault(vm => vm.Device == e.Device); - ListDeviceViewModel? listVm = ListDeviceViewModels.FirstOrDefault(vm => vm.Device == e.Device); - if (surfaceVm != null) - SurfaceDeviceViewModels.Remove(surfaceVm); - if (listVm != null) - ListDeviceViewModels.Remove(listVm); - } - public bool ColorDevices { get => _colorDevices; @@ -180,6 +159,27 @@ public class SurfaceEditorViewModel : MainScreenViewModel surfaceDeviceViewModel.UpdateMouseDrag(mousePosition, round, ignoreOverlap); } + private void RgbServiceOnDeviceAdded(object? sender, DeviceEventArgs e) + { + if (!e.Device.IsEnabled) + return; + + SurfaceDeviceViewModels.Add(_surfaceVmFactory.SurfaceDeviceViewModel(e.Device, this)); + ListDeviceViewModels.Add(_surfaceVmFactory.ListDeviceViewModel(e.Device, this)); + SurfaceDeviceViewModels.Sort(l => l.Device.ZIndex * -1); + ListDeviceViewModels.Sort(l => l.Device.ZIndex * -1); + } + + private void RgbServiceOnDeviceRemoved(object? sender, DeviceEventArgs e) + { + SurfaceDeviceViewModel? surfaceVm = SurfaceDeviceViewModels.FirstOrDefault(vm => vm.Device == e.Device); + ListDeviceViewModel? listVm = ListDeviceViewModels.FirstOrDefault(vm => vm.Device == e.Device); + if (surfaceVm != null) + SurfaceDeviceViewModels.Remove(surfaceVm); + if (listVm != null) + ListDeviceViewModels.Remove(listVm); + } + private async Task ExecuteAutoArrange() { bool confirmed = await _windowService.ShowConfirmContentDialog("Auto-arrange layout", "Are you sure you want to auto-arrange your layout? Your current settings will be overwritten."); diff --git a/src/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs b/src/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs index 6bbdb5ac0..039d5f4fe 100644 --- a/src/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs +++ b/src/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs @@ -50,10 +50,10 @@ public class NodeScriptViewModel : ActivatableViewModelBase this.WhenActivated(d => { Observable.FromEventPattern>(x => NodeScript.NodeAdded += x, x => NodeScript.NodeAdded -= x) - .Subscribe(e => HandleNodeAdded(e.EventArgs)) + .Subscribe(e => _nodeViewModels.Add(_nodeVmFactory.NodeViewModel(this, e.EventArgs.Value))) .DisposeWith(d); Observable.FromEventPattern>(x => NodeScript.NodeRemoved += x, x => NodeScript.NodeRemoved -= x) - .Subscribe(e => HandleNodeRemoved(e.EventArgs)) + .Subscribe(e => _nodeViewModels.RemoveMany(_nodeViewModels.Items.Where(n => n.Node == e.EventArgs.Value))) .DisposeWith(d); }); @@ -276,16 +276,4 @@ public class NodeScriptViewModel : ActivatableViewModelBase private void ExecutePasteSelected() { } - - private void HandleNodeAdded(SingleValueEventArgs eventArgs) - { - _nodeViewModels.Add(_nodeVmFactory.NodeViewModel(this, eventArgs.Value)); - } - - private void HandleNodeRemoved(SingleValueEventArgs eventArgs) - { - NodeViewModel? toRemove = NodeViewModels.FirstOrDefault(vm => ReferenceEquals(vm.Node, eventArgs.Value)); - if (toRemove != null) - _nodeViewModels.Remove(toRemove); - } } \ 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 09dc7d049..19eefefb8 100644 --- a/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowViewModel.cs +++ b/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowViewModel.cs @@ -12,7 +12,6 @@ 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,8 +24,8 @@ public class NodeScriptWindowViewModel : DialogViewModelBase { private readonly INodeEditorService _nodeEditorService; private readonly INodeService _nodeService; - private readonly ISettingsService _settingsService; private readonly IProfileService _profileService; + private readonly ISettingsService _settingsService; private readonly IWindowService _windowService; public NodeScriptWindowViewModel(NodeScript nodeScript, @@ -66,10 +65,10 @@ public class NodeScriptWindowViewModel : DialogViewModelBase 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(); saveTimer.Start(); - + Disposable.Create(() => { updateTimer.Stop(); diff --git a/src/Artemis.UI/Screens/VisualScripting/NodeView.axaml b/src/Artemis.UI/Screens/VisualScripting/NodeView.axaml index aebe108cf..40849f3d2 100644 --- a/src/Artemis.UI/Screens/VisualScripting/NodeView.axaml +++ b/src/Artemis.UI/Screens/VisualScripting/NodeView.axaml @@ -37,35 +37,38 @@ ClipToBounds="True" Background="{DynamicResource ContentDialogBackground}"> - - - - + + + + - - diff --git a/src/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs b/src/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs index bb3a1670d..e8777e411 100644 --- a/src/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs +++ b/src/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs @@ -8,6 +8,7 @@ using Artemis.Core.Events; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.VisualScripting.Pins; using Artemis.UI.Shared; +using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services.NodeEditor; using Artemis.UI.Shared.Services.NodeEditor.Commands; using Avalonia; @@ -21,9 +22,9 @@ namespace Artemis.UI.Screens.VisualScripting; public class NodeViewModel : ActivatableViewModelBase { private readonly INodeEditorService _nodeEditorService; + private readonly IWindowService _windowService; private ICustomNodeViewModel? _customNodeViewModel; - private ReactiveCommand? _deleteNode; private double _dragOffsetX; private double _dragOffsetY; private ObservableAsPropertyHelper? _hasInputPins; @@ -34,14 +35,13 @@ public class NodeViewModel : ActivatableViewModelBase private double _startX; private double _startY; - public NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService) + public NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService, IWindowService windowService) { _nodeEditorService = nodeEditorService; + _windowService = windowService; NodeScriptViewModel = nodeScriptViewModel; Node = node; - DeleteNode = ReactiveCommand.Create(ExecuteDeleteNode, this.WhenAnyValue(vm => vm.IsStaticNode).Select(v => !v)); - SourceList nodePins = new(); SourceList nodePinCollections = new(); @@ -62,6 +62,9 @@ public class NodeViewModel : ActivatableViewModelBase PinViewModels = pins; + DeleteNode = ReactiveCommand.Create(ExecuteDeleteNode, this.WhenAnyValue(vm => vm.IsStaticNode).Select(v => !v)); + ShowBrokenState = ReactiveCommand.Create(ExecuteShowBrokenState); + this.WhenActivated(d => { _isStaticNode = Node.WhenAnyValue(n => n.IsDefaultNode, n => n.IsExitNode) @@ -90,7 +93,7 @@ public class NodeViewModel : ActivatableViewModelBase }) .DisposeWith(d); Observable.FromEventPattern>(x => Node.PinRemoved += x, x => Node.PinRemoved -= x) - .Subscribe(p => nodePins!.Remove(nodePins.Items.FirstOrDefault(vm => vm.Pin == p.EventArgs.Value))) + .Subscribe(p => nodePins.RemoveMany(nodePins.Items.Where(vm => vm.Pin == p.EventArgs.Value))) .DisposeWith(d); nodePins.Edit(l => { @@ -115,7 +118,7 @@ public class NodeViewModel : ActivatableViewModelBase }) .DisposeWith(d); Observable.FromEventPattern>(x => Node.PinCollectionRemoved += x, x => Node.PinCollectionRemoved -= x) - .Subscribe(p => nodePinCollections!.Remove(nodePinCollections.Items.FirstOrDefault(vm => vm.PinCollection == p.EventArgs.Value))) + .Subscribe(p => nodePinCollections.RemoveMany(nodePinCollections.Items.Where(vm => vm.PinCollection == p.EventArgs.Value))) .DisposeWith(d); nodePinCollections.Edit(l => { @@ -152,18 +155,15 @@ public class NodeViewModel : ActivatableViewModelBase set => RaiseAndSetIfChanged(ref _customNodeViewModel, value); } - public ReactiveCommand? DeleteNode - { - get => _deleteNode; - set => RaiseAndSetIfChanged(ref _deleteNode, value); - } - public bool IsSelected { get => _isSelected; set => RaiseAndSetIfChanged(ref _isSelected, value); } + public ReactiveCommand ShowBrokenState { get; } + public ReactiveCommand DeleteNode { get; } + public void StartDrag(Point mouseStartPosition) { if (!IsSelected) @@ -195,4 +195,10 @@ public class NodeViewModel : ActivatableViewModelBase { _nodeEditorService.ExecuteCommand(NodeScriptViewModel.NodeScript, new DeleteNode(NodeScriptViewModel.NodeScript, Node)); } + + private void ExecuteShowBrokenState() + { + if (Node.BrokenState != null && Node.BrokenStateException != null) + _windowService.ShowExceptionDialog(Node.BrokenState, Node.BrokenStateException); + } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/VisualScripting/Pins/PinCollectionViewModel.cs b/src/Artemis.UI/Screens/VisualScripting/Pins/PinCollectionViewModel.cs index 782e80131..d6e160dc4 100644 --- a/src/Artemis.UI/Screens/VisualScripting/Pins/PinCollectionViewModel.cs +++ b/src/Artemis.UI/Screens/VisualScripting/Pins/PinCollectionViewModel.cs @@ -30,7 +30,7 @@ public abstract class PinCollectionViewModel : ActivatableViewModelBase .Subscribe(e => PinViewModels.Add(CreatePinViewModel(e.EventArgs.Value))) .DisposeWith(d); Observable.FromEventPattern>(x => PinCollection.PinRemoved += x, x => PinCollection.PinRemoved -= x) - .Subscribe(e => PinViewModels.RemoveMany(PinViewModels.Where(p => p.Pin == e.EventArgs.Value).ToList())) + .Subscribe(e => PinViewModels.RemoveMany(PinViewModels.Where(p => p.Pin == e.EventArgs.Value))) .DisposeWith(d); }); diff --git a/src/Artemis.UI/Screens/VisualScripting/Pins/PinViewModel.cs b/src/Artemis.UI/Screens/VisualScripting/Pins/PinViewModel.cs index 8eae87607..37542daca 100644 --- a/src/Artemis.UI/Screens/VisualScripting/Pins/PinViewModel.cs +++ b/src/Artemis.UI/Screens/VisualScripting/Pins/PinViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Reactive; using System.Reactive.Linq; using Artemis.Core; @@ -38,7 +39,7 @@ public abstract class PinViewModel : ActivatableViewModelBase .Subscribe(e => connectedPins.Add(e.EventArgs.Value)) .DisposeWith(d); Observable.FromEventPattern>(x => Pin.PinDisconnected += x, x => Pin.PinDisconnected -= x) - .Subscribe(e => connectedPins.Remove(e.EventArgs.Value)) + .Subscribe(e => connectedPins.RemoveMany(connectedPins.Items.Where(p => p == e.EventArgs.Value))) .DisposeWith(d); Pin.WhenAnyValue(p => p.Type).Subscribe(_ => UpdatePinColor()).DisposeWith(d); }); diff --git a/src/Artemis.VisualScripting/Nodes/Text/StringRegexMatchNode.cs b/src/Artemis.VisualScripting/Nodes/Text/StringRegexMatchNode.cs index c3c1d1a8b..810080877 100644 --- a/src/Artemis.VisualScripting/Nodes/Text/StringRegexMatchNode.cs +++ b/src/Artemis.VisualScripting/Nodes/Text/StringRegexMatchNode.cs @@ -8,7 +8,7 @@ public class StringRegexMatchNode : Node { private string? _lastPattern; private Regex? _regex; - private bool _broken; + private Exception? _exception; public StringRegexMatchNode() : base("Regex Match", "Checks provided regex pattern matches the input.") { @@ -25,20 +25,27 @@ public class StringRegexMatchNode : Node { if (Input.Value == null || Pattern.Value == null) return; - if (_broken && _lastPattern == Pattern.Value) - return; + // If the regex was invalid output false and rethrow the exception + if (_lastPattern == Pattern.Value && _exception != null) + { + Result.Value = false; + throw _exception; + } + + // If there is no regex yet or the regex changed, recompile if (_regex == null || _lastPattern != Pattern.Value) { try { _regex = new Regex(Pattern.Value, RegexOptions.Compiled); - _broken = false; + _exception = null; } - catch (Exception) + catch (Exception e) { - _broken = true; - return; + // If there is an exception, save it to keep rethrowing until the regex is fixed + _exception = e; + throw; } finally {