1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Merge branch 'development'

This commit is contained in:
Robert 2022-09-03 10:46:01 +02:00
commit 76994ce596
48 changed files with 309 additions and 273 deletions

View File

@ -91,11 +91,6 @@ public static class Constants
/// </summary> /// </summary>
public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), null); public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), null);
/// <summary>
/// Gets the startup arguments provided to the application
/// </summary>
public static ReadOnlyCollection<string> StartupArguments { get; set; } = null!;
internal static readonly CorePluginFeature CorePluginFeature = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Core")}; 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")}; internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Effect Placeholder")};
@ -153,6 +148,11 @@ public static class Constants
typeof(decimal) typeof(decimal)
}; };
/// <summary>
/// Gets the startup arguments provided to the application
/// </summary>
public static ReadOnlyCollection<string> StartupArguments { get; set; } = null!;
/// <summary> /// <summary>
/// Gets the graphics context to be used for rendering by SkiaSharp. Can be set via /// Gets the graphics context to be used for rendering by SkiaSharp. Can be set via
/// <see cref="IRgbService.UpdateGraphicsContext" />. /// <see cref="IRgbService.UpdateGraphicsContext" />.

View File

@ -1,7 +1,7 @@
using System; using System;
namespace Artemis.Core namespace Artemis.Core;
{
/// <summary> /// <summary>
/// Provides data for layer property events. /// Provides data for layer property events.
/// </summary> /// </summary>
@ -17,4 +17,3 @@ namespace Artemis.Core
/// </summary> /// </summary>
public ILayerPropertyKeyframe Keyframe { get; } public ILayerPropertyKeyframe Keyframe { get; }
} }
}

View File

@ -69,11 +69,16 @@ public abstract class BreakableModel : CorePropertyChanged, IBreakableModel
/// <inheritdoc /> /// <inheritdoc />
public void ClearBrokenState(string state) 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) if (BrokenState == null)
return; return;
// Only clear similar broken states
if (BrokenState != state)
return;
if (BrokenState != state) return;
BrokenState = null; BrokenState = null;
BrokenStateException = null; BrokenStateException = null;
OnBrokenStateChanged(); OnBrokenStateChanged();

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
namespace Artemis.Core.Services; namespace Artemis.Core.Services;

View File

@ -115,7 +115,7 @@ internal class NodeService : INodeService
} }
} }
node.Initialize(script); node.TryInitialize(script);
return node; return node;
} }

View File

@ -233,7 +233,7 @@ internal class PluginManagementService : IPluginManagementService
} }
catch (Exception e) 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");
} }
} }

View File

@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Diagnostics;
namespace Artemis.Core.Services;
internal class ProcessComparer : IEqualityComparer<Process>
{
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;
}
}

View File

@ -4,7 +4,6 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Timers; using System.Timers;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using Serilog;
namespace Artemis.Core.Services; namespace Artemis.Core.Services;
@ -43,19 +42,3 @@ internal class ProcessMonitorService : IProcessMonitorService
return _lastScannedProcesses; return _lastScannedProcesses;
} }
} }
internal class ProcessComparer : IEqualityComparer<Process>
{
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;
}
}

View File

@ -38,6 +38,24 @@ internal class WebServerService : IWebServerService, IDisposable
StartWebServer(); 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) private void WebServerEnabledSettingOnSettingChanged(object? sender, EventArgs e)
{ {
StartWebServer(); StartWebServer();
@ -76,6 +94,7 @@ internal class WebServerService : IWebServerService, IDisposable
public WebServer? Server { get; private set; } public WebServer? Server { get; private set; }
public PluginsModule PluginsModule { get; } public PluginsModule PluginsModule { get; }
public event EventHandler? WebServerStarting;
#region Web server managament #region Web server managament
@ -302,27 +321,4 @@ internal class WebServerService : IWebServerService, IDisposable
} }
#endregion #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
} }

View File

@ -62,9 +62,10 @@ internal class NodeTypeStore
public static Plugin? GetPlugin(INode node) public static Plugin? GetPlugin(INode node)
{ {
Type nodeType = node.GetType();
lock (Registrations) lock (Registrations)
{ {
return Registrations.FirstOrDefault(r => r.Plugin.GetType().Assembly == node.GetType().Assembly)?.Plugin; return Registrations.FirstOrDefault(r => r.NodeData.Type == nodeType)?.Plugin;
} }
} }

View File

@ -160,10 +160,25 @@ public readonly struct Numeric : IComparable<Numeric>, IConvertible
return (byte) Math.Clamp(p._value, 0, 255); return (byte) Math.Clamp(p._value, 0, 255);
} }
public static implicit operator Numeric(double d) => new(d); public static implicit operator Numeric(double d)
public static implicit operator Numeric(float f) => new(f); {
public static implicit operator Numeric(int i) => new(i); return new Numeric(d);
public static implicit operator Numeric(byte b) => new(b); }
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) public static implicit operator long(Numeric p)
{ {

View File

@ -1,6 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Artemis.Core; namespace Artemis.Core;
@ -111,10 +109,14 @@ public sealed class InputPin : Pin
Value = Type.GetDefault()!; Value = Type.GetDefault()!;
} }
else if (ConnectedTo.Count > 0) else if (ConnectedTo.Count > 0)
{
Value = ConnectedTo[0].PinValue; Value = ConnectedTo[0].PinValue;
}
else else
{
Value = null; Value = null;
} }
}
#endregion #endregion

View File

@ -8,7 +8,7 @@ namespace Artemis.Core;
/// <summary> /// <summary>
/// Represents a kind of node inside a <see cref="INodeScript" /> /// Represents a kind of node inside a <see cref="INodeScript" />
/// </summary> /// </summary>
public interface INode : INotifyPropertyChanged public interface INode : INotifyPropertyChanged, IBreakableModel
{ {
/// <summary> /// <summary>
/// Gets or sets the ID of the node. /// Gets or sets the ID of the node.
@ -81,15 +81,15 @@ public interface INode : INotifyPropertyChanged
event EventHandler<SingleValueEventArgs<IPinCollection>> PinCollectionRemoved; event EventHandler<SingleValueEventArgs<IPinCollection>> PinCollectionRemoved;
/// <summary> /// <summary>
/// Called when the node was loaded from storage or newly created /// Attempts to initialize the node.
/// </summary> /// </summary>
/// <param name="script">The script the node is contained in</param> /// <param name="script">The script the node is contained in</param>
void Initialize(INodeScript script); void TryInitialize(INodeScript script);
/// <summary> /// <summary>
/// Evaluates the value of the output pins of this node /// Attempts to evaluate the value of the output pins of this node
/// </summary> /// </summary>
void Evaluate(); void TryEvaluate();
/// <summary> /// <summary>
/// Resets the node causing all pins to re-evaluate the next time <see cref="Evaluate" /> is called /// Resets the node causing all pins to re-evaluate the next time <see cref="Evaluate" /> is called

View File

@ -85,7 +85,10 @@ public interface IPin
/// Determines whether this pin is compatible with the given type /// Determines whether this pin is compatible with the given type
/// </summary> /// </summary>
/// <param name="type">The type to check for compatibility</param> /// <param name="type">The type to check for compatibility</param>
/// <param name="forgivingEnumMatching">A boolean indicating whether or not enums should be exactly equal or just both be enums</param> /// <param name="forgivingEnumMatching">
/// A boolean indicating whether or not enums should be exactly equal or just both be
/// enums
/// </param>
/// <returns><see langword="true" /> if the type is compatible, otherwise <see langword="false" />.</returns> /// <returns><see langword="true" /> if the type is compatible, otherwise <see langword="false" />.</returns>
public bool IsTypeCompatible(Type type, bool forgivingEnumMatching = true); public bool IsTypeCompatible(Type type, bool forgivingEnumMatching = true);
} }

View File

@ -26,6 +26,17 @@ internal class DataBindingExitNode<TLayerProperty> : Node, IExitNode
property.SetValue(pendingValue); 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() private void ClearInputPins()
{ {
while (Pins.Any()) while (Pins.Any())
@ -59,15 +70,4 @@ internal class DataBindingExitNode<TLayerProperty> : Node, IExitNode
} }
public override bool IsExitNode => true; 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);
}
}
} }

View File

@ -12,7 +12,7 @@ namespace Artemis.Core;
/// <summary> /// <summary>
/// Represents a kind of node inside a <see cref="NodeScript" /> /// Represents a kind of node inside a <see cref="NodeScript" />
/// </summary> /// </summary>
public abstract class Node : CorePropertyChanged, INode public abstract class Node : BreakableModel, INode
{ {
/// <inheritdoc /> /// <inheritdoc />
public event EventHandler? Resetting; public event EventHandler? Resetting;
@ -95,6 +95,9 @@ public abstract class Node : CorePropertyChanged, INode
/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyCollection<IPinCollection> PinCollections => new ReadOnlyCollection<IPinCollection>(_pinCollections); public IReadOnlyCollection<IPinCollection> PinCollections => new ReadOnlyCollection<IPinCollection>(_pinCollections);
/// <inheritdoc />
public override string BrokenDisplayName => Name;
#endregion #endregion
#region Construtors #region Construtors
@ -335,12 +338,17 @@ public abstract class Node : CorePropertyChanged, INode
return isRemoved; return isRemoved;
} }
/// <inheritdoc /> /// <summary>
/// Called when the node was loaded from storage or newly created
/// </summary>
/// <param name="script">The script the node is contained in</param>
public virtual void Initialize(INodeScript script) public virtual void Initialize(INodeScript script)
{ {
} }
/// <inheritdoc /> /// <summary>
/// Evaluates the value of the output pins of this node
/// </summary>
public abstract void Evaluate(); public abstract void Evaluate();
/// <inheritdoc /> /// <inheritdoc />
@ -354,6 +362,18 @@ public abstract class Node : CorePropertyChanged, INode
Resetting?.Invoke(this, EventArgs.Empty); Resetting?.Invoke(this, EventArgs.Empty);
} }
/// <inheritdoc />
public void TryInitialize(INodeScript script)
{
TryOrBreak(() => Initialize(script), "Failed to initialize");
}
/// <inheritdoc />
public void TryEvaluate()
{
TryOrBreak(Evaluate, "Failed to evaluate");
}
/// <summary> /// <summary>
/// Called whenever the node must show it's custom view model, if <see langword="null" />, no custom view model is used /// Called whenever the node must show it's custom view model, if <see langword="null" />, no custom view model is used
/// </summary> /// </summary>

View File

@ -14,13 +14,19 @@ namespace Artemis.Core;
/// </summary> /// </summary>
public abstract class NodeScript : CorePropertyChanged, INodeScript 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))) if (Entity.Nodes.Any(n => e.TypeRegistration.MatchesEntity(n)))
Load(); Load();
} }
private void NodeTypeStoreOnNodeTypeRemoved(object? sender, NodeTypeStoreEvent e)
{
List<INode> nodes = Nodes.Where(n => n.GetType() == e.TypeRegistration.NodeData.Type).ToList();
foreach (INode node in nodes)
RemoveNode(node);
}
/// <inheritdoc /> /// <inheritdoc />
public event EventHandler<SingleValueEventArgs<INode>>? NodeAdded; public event EventHandler<SingleValueEventArgs<INode>>? NodeAdded;
@ -79,8 +85,8 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
Entity = new NodeScriptEntity(); Entity = new NodeScriptEntity();
ExitNode = null!; ExitNode = null!;
NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeChanged; NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeAdded;
NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeChanged; NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeRemoved;
} }
internal NodeScript(string name, string description, NodeScriptEntity entity, object? context = null) internal NodeScript(string name, string description, NodeScriptEntity entity, object? context = null)
@ -91,8 +97,8 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
Context = context; Context = context;
ExitNode = null!; ExitNode = null!;
NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeChanged; NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeAdded;
NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeChanged; NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeRemoved;
} }
#endregion #endregion
@ -108,7 +114,7 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
node.Reset(); node.Reset();
} }
ExitNode.Evaluate(); ExitNode.TryEvaluate();
} }
/// <inheritdoc /> /// <inheritdoc />
@ -136,8 +142,8 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {
NodeTypeStore.NodeTypeAdded -= NodeTypeStoreOnNodeTypeChanged; NodeTypeStore.NodeTypeAdded -= NodeTypeStoreOnNodeTypeAdded;
NodeTypeStore.NodeTypeRemoved -= NodeTypeStoreOnNodeTypeChanged; NodeTypeStore.NodeTypeRemoved -= NodeTypeStoreOnNodeTypeRemoved;
lock (_nodes) lock (_nodes)
{ {

View File

@ -1,6 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Artemis.Core; namespace Artemis.Core;
@ -43,7 +41,7 @@ public sealed class OutputPin<T> : Pin
get get
{ {
if (!IsEvaluated) if (!IsEvaluated)
Node?.Evaluate(); Node?.TryEvaluate();
return _value; return _value;
} }
@ -115,7 +113,7 @@ public sealed class OutputPin : Pin
get get
{ {
if (!IsEvaluated) if (!IsEvaluated)
Node?.Evaluate(); Node?.TryEvaluate();
return _value; return _value;
} }

View File

@ -143,9 +143,11 @@ public abstract class Pin : CorePropertyChanged, IPin
/// <param name="currentType">The backing field of the current type of the pin.</param> /// <param name="currentType">The backing field of the current type of the pin.</param>
protected void ChangeType(Type type, ref Type currentType) protected void ChangeType(Type type, ref Type currentType)
{ {
// Enums are a special case that disconnect and, if still compatible, reconnect if (currentType == type)
if (type.IsEnum && currentType.IsEnum) return;
{
bool changingEnums = type.IsEnum && currentType.IsEnum;
List<IPin> connections = new(ConnectedTo); List<IPin> connections = new(ConnectedTo);
DisconnectAll(); DisconnectAll();
@ -153,21 +155,9 @@ public abstract class Pin : CorePropertyChanged, IPin
SetAndNotify(ref currentType, type, nameof(Type)); SetAndNotify(ref currentType, type, nameof(Type));
IsNumeric = type == typeof(Numeric); IsNumeric = type == typeof(Numeric);
foreach (IPin pin in connections.Where(p => p.IsTypeCompatible(type))) foreach (IPin pin in connections.Where(p => p.IsTypeCompatible(type, changingEnums)))
ConnectTo(pin); ConnectTo(pin);
} }
// Disconnect pins incompatible with the new type
else
{
List<IPin> toDisconnect = ConnectedTo.Where(p => !p.IsTypeCompatible(type, false)).ToList();
foreach (IPin pin in toDisconnect)
DisconnectFrom(pin);
// Change the type
SetAndNotify(ref currentType, type, nameof(Type));
IsNumeric = type == typeof(Numeric);
}
}
#endregion #endregion
} }

View File

@ -33,14 +33,12 @@ public class LinuxInputProvider : InputProvider
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
if (disposing) if (disposing)
{
for (int i = _readers.Count - 1; i >= 0; i--) for (int i = _readers.Count - 1; i >= 0; i--)
{ {
_readers[i].InputEvent -= OnInputEvent; _readers[i].InputEvent -= OnInputEvent;
_readers[i].Dispose(); _readers[i].Dispose();
_readers.RemoveAt(i); _readers.RemoveAt(i);
} }
}
base.Dispose(disposing); base.Dispose(disposing);
} }

View File

@ -4,7 +4,6 @@ using Avalonia.Controls;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Xaml.Interactivity; using Avalonia.Xaml.Interactivity;
using FluentAvalonia.UI.Controls;
namespace Artemis.UI.Shared.Behaviors; namespace Artemis.UI.Shared.Behaviors;

View File

@ -19,12 +19,12 @@ public interface IMainWindowService : IArtemisSharedUIService
void ConfigureMainWindowProvider(IMainWindowProvider mainWindowProvider); void ConfigureMainWindowProvider(IMainWindowProvider mainWindowProvider);
/// <summary> /// <summary>
/// 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
/// </summary> /// </summary>
void OpenMainWindow(); void OpenMainWindow();
/// <summary> /// <summary>
/// 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
/// </summary> /// </summary>
void CloseMainWindow(); void CloseMainWindow();

View File

@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Linq;
using System.Linq;
using Artemis.Core; using Artemis.Core;
namespace Artemis.UI.Shared.Services.NodeEditor.Commands; namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
@ -9,9 +8,9 @@ namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
/// </summary> /// </summary>
public class ConnectPins : INodeEditorCommand public class ConnectPins : INodeEditorCommand
{ {
private readonly IPin _output;
private readonly IPin _input; private readonly IPin _input;
private readonly IPin? _originalConnection; private readonly IPin? _originalConnection;
private readonly IPin _output;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="ConnectPins" /> class. /// Creates a new instance of the <see cref="ConnectPins" /> class.

View File

@ -24,11 +24,6 @@ public class App : Application
private StandardKernel? _kernel; private StandardKernel? _kernel;
private bool _shutDown; private bool _shutDown;
// ReSharper disable NotAccessedField.Local
private ApplicationStateManager? _applicationStateManager;
private Mutex? _artemisMutex;
// ReSharper restore NotAccessedField.Local
public override void Initialize() public override void Initialize()
{ {
// If Artemis is already running, bring it to foreground and stop this process // 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
} }

View File

@ -17,10 +17,10 @@ public class WindowsInputProvider : InputProvider
private readonly IInputService _inputService; private readonly IInputService _inputService;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly SpongeWindow _sponge;
private readonly Timer _taskManagerTimer; private readonly Timer _taskManagerTimer;
private DateTime _lastMouseUpdate; private DateTime _lastMouseUpdate;
private int _lastProcessId; private int _lastProcessId;
private readonly SpongeWindow _sponge;
public WindowsInputProvider(ILogger logger, IInputService inputService) public WindowsInputProvider(ILogger logger, IInputService inputService)
{ {

View File

@ -24,8 +24,8 @@ namespace Artemis.UI.Windows.Providers;
public class UpdateProvider : IUpdateProvider, IDisposable public class UpdateProvider : IUpdateProvider, IDisposable
{ {
private const string ApiUrl = "https://dev.azure.com/artemis-rgb/Artemis/_apis/"; private const string API_URL = "https://dev.azure.com/artemis-rgb/Artemis/_apis/";
private const string InstallerUrl = "https://builds.artemis-rgb.com/binaries/Artemis.Installer.exe"; private const string INSTALLER_URL = "https://builds.artemis-rgb.com/binaries/Artemis.Installer.exe";
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IMainWindowService _mainWindowService; private readonly IMainWindowService _mainWindowService;
@ -42,7 +42,7 @@ public class UpdateProvider : IUpdateProvider, IDisposable
public async Task<DevOpsBuild?> GetBuildInfo(int buildDefinition, string? buildNumber = null) public async Task<DevOpsBuild?> GetBuildInfo(int buildDefinition, string? buildNumber = null)
{ {
Url request = ApiUrl.AppendPathSegments("build", "builds") Url request = API_URL.AppendPathSegments("build", "builds")
.SetQueryParam("definitions", buildDefinition) .SetQueryParam("definitions", buildDefinition)
.SetQueryParam("resultFilter", "succeeded") .SetQueryParam("resultFilter", "succeeded")
.SetQueryParam("$top", 1) .SetQueryParam("$top", 1)
@ -143,9 +143,9 @@ public class UpdateProvider : IUpdateProvider, IDisposable
string installerDirectory = Path.Combine(Constants.DataFolder, "installer"); string installerDirectory = Path.Combine(Constants.DataFolder, "installer");
string installerPath = Path.Combine(installerDirectory, "Artemis.Installer.exe"); 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(); using HttpClient client = new();
HttpResponseMessage httpResponseMessage = await client.GetAsync(InstallerUrl); HttpResponseMessage httpResponseMessage = await client.GetAsync(INSTALLER_URL);
if (!httpResponseMessage.IsSuccessStatusCode) if (!httpResponseMessage.IsSuccessStatusCode)
throw new ArtemisUIException($"Failed to download installer, status code {httpResponseMessage.StatusCode}"); throw new ArtemisUIException($"Failed to download installer, status code {httpResponseMessage.StatusCode}");

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive; using System.Reactive;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Ninject; using Artemis.Core.Ninject;

View File

@ -2,6 +2,7 @@ using System;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Shared.Services.MainWindow; using Artemis.UI.Shared.Services.MainWindow;
using Avalonia.Threading;
using EmbedIO; using EmbedIO;
using EmbedIO.Routing; using EmbedIO.Routing;
using EmbedIO.WebApi; using EmbedIO.WebApi;
@ -22,7 +23,7 @@ public class RemoteController : WebApiController
[Route(HttpVerbs.Post, "/remote/bring-to-foreground")] [Route(HttpVerbs.Post, "/remote/bring-to-foreground")]
public void PostBringToForeground() public void PostBringToForeground()
{ {
_mainWindowService.OpenMainWindow(); Dispatcher.UIThread.Post(() => _mainWindowService.OpenMainWindow());
} }
[Route(HttpVerbs.Post, "/remote/restart")] [Route(HttpVerbs.Post, "/remote/restart")]

View File

@ -37,6 +37,16 @@ public class DevicePropertiesViewModel : DialogViewModelBase<object>
ClearSelectedLeds = ReactiveCommand.Create(ExecuteClearSelectedLeds); ClearSelectedLeds = ReactiveCommand.Create(ExecuteClearSelectedLeds);
} }
public ArtemisDevice Device
{
get => _device;
set => RaiseAndSetIfChanged(ref _device, value);
}
public ObservableCollection<ArtemisLed> SelectedLeds { get; }
public ObservableCollection<ActivatableViewModelBase> Tabs { get; }
public ReactiveCommand<Unit, Unit> ClearSelectedLeds { get; }
private void RgbServiceOnDeviceAdded(object? sender, DeviceEventArgs e) private void RgbServiceOnDeviceAdded(object? sender, DeviceEventArgs e)
{ {
if (e.Device.Identifier != Device.Identifier || Device == e.Device) if (e.Device.Identifier != Device.Identifier || Device == e.Device)
@ -52,16 +62,6 @@ public class DevicePropertiesViewModel : DialogViewModelBase<object>
SelectedLeds.Clear(); SelectedLeds.Clear();
} }
public ArtemisDevice Device
{
get => _device;
set => RaiseAndSetIfChanged(ref _device, value);
}
public ObservableCollection<ArtemisLed> SelectedLeds { get; }
public ObservableCollection<ActivatableViewModelBase> Tabs { get; }
public ReactiveCommand<Unit, Unit> ClearSelectedLeds { get; }
private void AddTabs() private void AddTabs()
{ {
Tabs.Add(_deviceVmFactory.DevicePropertiesTabViewModel(Device)); Tabs.Add(_deviceVmFactory.DevicePropertiesTabViewModel(Device));

View File

@ -32,7 +32,7 @@ public class LayerHintsDialogViewModel : DialogViewModelBase<bool>
.Subscribe(c => AdaptionHints.Add(CreateHintViewModel(c.EventArgs.AdaptionHint))) .Subscribe(c => AdaptionHints.Add(CreateHintViewModel(c.EventArgs.AdaptionHint)))
.DisposeWith(d); .DisposeWith(d);
Observable.FromEventPattern<LayerAdapterHintEventArgs>(x => layer.Adapter.AdapterHintRemoved += x, x => layer.Adapter.AdapterHintRemoved -= x) Observable.FromEventPattern<LayerAdapterHintEventArgs>(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); .DisposeWith(d);
AdaptionHints.AddRange(Layer.Adapter.AdaptionHints.Select(CreateHintViewModel)); AdaptionHints.AddRange(Layer.Adapter.AdaptionHints.Select(CreateHintViewModel));

View File

@ -15,10 +15,10 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree;
public class ProfileTreeView : ReactiveUserControl<ProfileTreeViewModel> public class ProfileTreeView : ReactiveUserControl<ProfileTreeViewModel>
{ {
private readonly TreeView _treeView;
private Image? _dragAdorner; private Image? _dragAdorner;
private Point _dragStartPosition; private Point _dragStartPosition;
private Point _elementDragOffset; private Point _elementDragOffset;
private readonly TreeView _treeView;
public ProfileTreeView() public ProfileTreeView()
{ {

View File

@ -293,6 +293,7 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
CanPaste = false; CanPaste = false;
return; return;
} }
CanPaste = formats.Contains(ProfileElementExtensions.ClipboardDataFormat); CanPaste = formats.Contains(ProfileElementExtensions.ClipboardDataFormat);
} }

View File

@ -22,10 +22,10 @@ public class DataBindingViewModel : ActivatableViewModelBase
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private ObservableAsPropertyHelper<bool>? _dataBindingEnabled; private ObservableAsPropertyHelper<bool>? _dataBindingEnabled;
private bool _editorOpen;
private ObservableAsPropertyHelper<ILayerProperty?>? _layerProperty; private ObservableAsPropertyHelper<ILayerProperty?>? _layerProperty;
private ObservableAsPropertyHelper<NodeScriptViewModel?>? _nodeScriptViewModel; private ObservableAsPropertyHelper<NodeScriptViewModel?>? _nodeScriptViewModel;
private bool _playing; private bool _playing;
private bool _editorOpen;
public DataBindingViewModel(IProfileEditorService profileEditorService, INodeVmFactory nodeVmFactory, IWindowService windowService, ISettingsService settingsService) public DataBindingViewModel(IProfileEditorService profileEditorService, INodeVmFactory nodeVmFactory, IWindowService windowService, ISettingsService settingsService)
{ {

View File

@ -42,7 +42,7 @@ public class TimelinePropertyViewModel<T> : ActivatableViewModelBase, ITimelineP
.Subscribe(e => _keyframes.Add((LayerPropertyKeyframe<T>) e.EventArgs.Keyframe)) .Subscribe(e => _keyframes.Add((LayerPropertyKeyframe<T>) e.EventArgs.Keyframe))
.DisposeWith(d); .DisposeWith(d);
Observable.FromEventPattern<LayerPropertyKeyframeEventArgs>(x => LayerProperty.KeyframeRemoved += x, x => LayerProperty.KeyframeRemoved -= x) Observable.FromEventPattern<LayerPropertyKeyframeEventArgs>(x => LayerProperty.KeyframeRemoved += x, x => LayerProperty.KeyframeRemoved -= x)
.Subscribe(e => _keyframes.Remove((LayerPropertyKeyframe<T>) e.EventArgs.Keyframe)) .Subscribe(e => _keyframes.RemoveMany(_keyframes.Items.Where(k => k == e.EventArgs.Keyframe)))
.DisposeWith(d); .DisposeWith(d);
_keyframes.Edit(k => _keyframes.Edit(k =>

View File

@ -22,9 +22,9 @@ namespace Artemis.UI.Screens.ProfileEditor;
public class ProfileEditorViewModel : MainScreenViewModel public class ProfileEditorViewModel : MainScreenViewModel
{ {
private readonly IMainWindowService _mainWindowService;
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
private readonly IMainWindowService _mainWindowService;
private readonly SourceList<IToolViewModel> _tools; private readonly SourceList<IToolViewModel> _tools;
private DisplayConditionScriptViewModel? _displayConditionScriptViewModel; private DisplayConditionScriptViewModel? _displayConditionScriptViewModel;
private ObservableAsPropertyHelper<ProfileEditorHistory?>? _history; private ObservableAsPropertyHelper<ProfileEditorHistory?>? _history;

View File

@ -161,8 +161,6 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi
/// <inheritdoc /> /// <inheritdoc />
public void OpenMainWindow() public void OpenMainWindow()
{
Dispatcher.UIThread.Post(() =>
{ {
if (_lifeTime.MainWindow == null) if (_lifeTime.MainWindow == null)
{ {
@ -175,7 +173,6 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi
_lifeTime.MainWindow.WindowState = WindowState.Normal; _lifeTime.MainWindow.WindowState = WindowState.Normal;
_lifeTime.MainWindow.Activate(); _lifeTime.MainWindow.Activate();
OnMainWindowOpened(); OnMainWindowOpened();
});
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -64,7 +64,7 @@ public class SidebarCategoryViewModel : ActivatableViewModelBase
.Subscribe(e => profileConfigurations.Add(e.EventArgs.ProfileConfiguration)) .Subscribe(e => profileConfigurations.Add(e.EventArgs.ProfileConfiguration))
.DisposeWith(d); .DisposeWith(d);
Observable.FromEventPattern<ProfileConfigurationEventArgs>(x => profileCategory.ProfileConfigurationRemoved += x, x => profileCategory.ProfileConfigurationRemoved -= x) Observable.FromEventPattern<ProfileConfigurationEventArgs>(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); .DisposeWith(d);
profileEditorService.ProfileConfiguration.Subscribe(p => SelectedProfileConfiguration = ProfileConfigurations.FirstOrDefault(c => ReferenceEquals(c.ProfileConfiguration, p))) profileEditorService.ProfileConfiguration.Subscribe(p => SelectedProfileConfiguration = ProfileConfigurations.FirstOrDefault(c => ReferenceEquals(c.ProfileConfiguration, p)))

View File

@ -21,8 +21,8 @@ public class SurfaceEditorViewModel : MainScreenViewModel
private readonly IDeviceService _deviceService; private readonly IDeviceService _deviceService;
private readonly IDeviceVmFactory _deviceVmFactory; private readonly IDeviceVmFactory _deviceVmFactory;
private readonly IRgbService _rgbService; private readonly IRgbService _rgbService;
private readonly ISurfaceVmFactory _surfaceVmFactory;
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
private readonly ISurfaceVmFactory _surfaceVmFactory;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private bool _colorDevices; private bool _colorDevices;
private bool _colorFirstLedOnly; 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 public bool ColorDevices
{ {
get => _colorDevices; get => _colorDevices;
@ -180,6 +159,27 @@ public class SurfaceEditorViewModel : MainScreenViewModel
surfaceDeviceViewModel.UpdateMouseDrag(mousePosition, round, ignoreOverlap); 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() 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."); bool confirmed = await _windowService.ShowConfirmContentDialog("Auto-arrange layout", "Are you sure you want to auto-arrange your layout? Your current settings will be overwritten.");

View File

@ -50,10 +50,10 @@ public class NodeScriptViewModel : ActivatableViewModelBase
this.WhenActivated(d => this.WhenActivated(d =>
{ {
Observable.FromEventPattern<SingleValueEventArgs<INode>>(x => NodeScript.NodeAdded += x, x => NodeScript.NodeAdded -= x) Observable.FromEventPattern<SingleValueEventArgs<INode>>(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); .DisposeWith(d);
Observable.FromEventPattern<SingleValueEventArgs<INode>>(x => NodeScript.NodeRemoved += x, x => NodeScript.NodeRemoved -= x) Observable.FromEventPattern<SingleValueEventArgs<INode>>(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); .DisposeWith(d);
}); });
@ -276,16 +276,4 @@ public class NodeScriptViewModel : ActivatableViewModelBase
private void ExecutePasteSelected() private void ExecutePasteSelected()
{ {
} }
private void HandleNodeAdded(SingleValueEventArgs<INode> eventArgs)
{
_nodeViewModels.Add(_nodeVmFactory.NodeViewModel(this, eventArgs.Value));
}
private void HandleNodeRemoved(SingleValueEventArgs<INode> eventArgs)
{
NodeViewModel? toRemove = NodeViewModels.FirstOrDefault(vm => ReferenceEquals(vm.Node, eventArgs.Value));
if (toRemove != null)
_nodeViewModels.Remove(toRemove);
}
} }

View File

@ -12,7 +12,6 @@ using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.NodeEditor; using Artemis.UI.Shared.Services.NodeEditor;
using Artemis.UI.Shared.Services.NodeEditor.Commands; using Artemis.UI.Shared.Services.NodeEditor.Commands;
using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia; using Avalonia;
using Avalonia.Threading; using Avalonia.Threading;
using DynamicData; using DynamicData;
@ -25,8 +24,8 @@ public class NodeScriptWindowViewModel : DialogViewModelBase<bool>
{ {
private readonly INodeEditorService _nodeEditorService; private readonly INodeEditorService _nodeEditorService;
private readonly INodeService _nodeService; private readonly INodeService _nodeService;
private readonly ISettingsService _settingsService;
private readonly IProfileService _profileService; private readonly IProfileService _profileService;
private readonly ISettingsService _settingsService;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
public NodeScriptWindowViewModel(NodeScript nodeScript, public NodeScriptWindowViewModel(NodeScript nodeScript,

View File

@ -37,26 +37,29 @@
ClipToBounds="True" ClipToBounds="True"
Background="{DynamicResource ContentDialogBackground}"> Background="{DynamicResource ContentDialogBackground}">
<Border Background="{DynamicResource TaskDialogHeaderBackground}"> <Border Background="{DynamicResource TaskDialogHeaderBackground}">
<Grid Classes="node-header" <Grid Classes="node-header" VerticalAlignment="Top" ColumnDefinitions="Auto,*,Auto">
VerticalAlignment="Top" <Button Grid.Column="0"
ColumnDefinitions="*,Auto"> VerticalAlignment="Center"
<TextBlock VerticalAlignment="Center" IsVisible="{CompiledBinding Node.BrokenState, Converter={x:Static ObjectConverters.IsNotNull}}"
Margin="10 0 0 0"
Text="{CompiledBinding Node.Name}"
ToolTip.Tip="{CompiledBinding Node.Description}">
</TextBlock>
<Button VerticalAlignment="Center"
Classes="icon-button icon-button-small" Classes="icon-button icon-button-small"
Grid.Column="1" Foreground="White"
Margin="5" Background="#E74C4C"
Command="{CompiledBinding DeleteNode}"> BorderBrush="#E74C4C"
Margin="5 0 0 0"
ToolTip.Tip="{CompiledBinding Node.BrokenState}"
Command="{CompiledBinding ShowBrokenState}">
<avalonia:MaterialIcon Kind="AlertCircle"></avalonia:MaterialIcon>
</Button>
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="10 0 0 0" Text="{CompiledBinding Node.Name}" ToolTip.Tip="{CompiledBinding Node.Description}"/>
<Button Grid.Column="2" VerticalAlignment="Center" Classes="icon-button icon-button-small" Margin="5" Command="{CompiledBinding DeleteNode}">
<avalonia:MaterialIcon Kind="Close"></avalonia:MaterialIcon> <avalonia:MaterialIcon Kind="Close"></avalonia:MaterialIcon>
</Button> </Button>
</Grid> </Grid>
</Border> </Border>
</Border> </Border>
<Grid Grid.Row="1" ColumnDefinitions="Auto,*,Auto" Margin="4"> <Grid Grid.Row="1" ColumnDefinitions="Auto,*,Auto" Margin="4">
<StackPanel Grid.Column="0" IsVisible="{CompiledBinding HasInputPins}"> <StackPanel Grid.Column="0" IsVisible="{CompiledBinding HasInputPins}">
<ItemsControl Items="{CompiledBinding InputPinViewModels}" Margin="4 0" /> <ItemsControl Items="{CompiledBinding InputPinViewModels}" Margin="4 0" />

View File

@ -8,6 +8,7 @@ using Artemis.Core.Events;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.VisualScripting.Pins; using Artemis.UI.Screens.VisualScripting.Pins;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.NodeEditor; using Artemis.UI.Shared.Services.NodeEditor;
using Artemis.UI.Shared.Services.NodeEditor.Commands; using Artemis.UI.Shared.Services.NodeEditor.Commands;
using Avalonia; using Avalonia;
@ -21,9 +22,9 @@ namespace Artemis.UI.Screens.VisualScripting;
public class NodeViewModel : ActivatableViewModelBase public class NodeViewModel : ActivatableViewModelBase
{ {
private readonly INodeEditorService _nodeEditorService; private readonly INodeEditorService _nodeEditorService;
private readonly IWindowService _windowService;
private ICustomNodeViewModel? _customNodeViewModel; private ICustomNodeViewModel? _customNodeViewModel;
private ReactiveCommand<Unit, Unit>? _deleteNode;
private double _dragOffsetX; private double _dragOffsetX;
private double _dragOffsetY; private double _dragOffsetY;
private ObservableAsPropertyHelper<bool>? _hasInputPins; private ObservableAsPropertyHelper<bool>? _hasInputPins;
@ -34,14 +35,13 @@ public class NodeViewModel : ActivatableViewModelBase
private double _startX; private double _startX;
private double _startY; 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; _nodeEditorService = nodeEditorService;
_windowService = windowService;
NodeScriptViewModel = nodeScriptViewModel; NodeScriptViewModel = nodeScriptViewModel;
Node = node; Node = node;
DeleteNode = ReactiveCommand.Create(ExecuteDeleteNode, this.WhenAnyValue(vm => vm.IsStaticNode).Select(v => !v));
SourceList<PinViewModel> nodePins = new(); SourceList<PinViewModel> nodePins = new();
SourceList<PinCollectionViewModel> nodePinCollections = new(); SourceList<PinCollectionViewModel> nodePinCollections = new();
@ -62,6 +62,9 @@ public class NodeViewModel : ActivatableViewModelBase
PinViewModels = pins; PinViewModels = pins;
DeleteNode = ReactiveCommand.Create(ExecuteDeleteNode, this.WhenAnyValue(vm => vm.IsStaticNode).Select(v => !v));
ShowBrokenState = ReactiveCommand.Create(ExecuteShowBrokenState);
this.WhenActivated(d => this.WhenActivated(d =>
{ {
_isStaticNode = Node.WhenAnyValue(n => n.IsDefaultNode, n => n.IsExitNode) _isStaticNode = Node.WhenAnyValue(n => n.IsDefaultNode, n => n.IsExitNode)
@ -90,7 +93,7 @@ public class NodeViewModel : ActivatableViewModelBase
}) })
.DisposeWith(d); .DisposeWith(d);
Observable.FromEventPattern<SingleValueEventArgs<IPin>>(x => Node.PinRemoved += x, x => Node.PinRemoved -= x) Observable.FromEventPattern<SingleValueEventArgs<IPin>>(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); .DisposeWith(d);
nodePins.Edit(l => nodePins.Edit(l =>
{ {
@ -115,7 +118,7 @@ public class NodeViewModel : ActivatableViewModelBase
}) })
.DisposeWith(d); .DisposeWith(d);
Observable.FromEventPattern<SingleValueEventArgs<IPinCollection>>(x => Node.PinCollectionRemoved += x, x => Node.PinCollectionRemoved -= x) Observable.FromEventPattern<SingleValueEventArgs<IPinCollection>>(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); .DisposeWith(d);
nodePinCollections.Edit(l => nodePinCollections.Edit(l =>
{ {
@ -152,18 +155,15 @@ public class NodeViewModel : ActivatableViewModelBase
set => RaiseAndSetIfChanged(ref _customNodeViewModel, value); set => RaiseAndSetIfChanged(ref _customNodeViewModel, value);
} }
public ReactiveCommand<Unit, Unit>? DeleteNode
{
get => _deleteNode;
set => RaiseAndSetIfChanged(ref _deleteNode, value);
}
public bool IsSelected public bool IsSelected
{ {
get => _isSelected; get => _isSelected;
set => RaiseAndSetIfChanged(ref _isSelected, value); set => RaiseAndSetIfChanged(ref _isSelected, value);
} }
public ReactiveCommand<Unit, Unit> ShowBrokenState { get; }
public ReactiveCommand<Unit, Unit> DeleteNode { get; }
public void StartDrag(Point mouseStartPosition) public void StartDrag(Point mouseStartPosition)
{ {
if (!IsSelected) if (!IsSelected)
@ -195,4 +195,10 @@ public class NodeViewModel : ActivatableViewModelBase
{ {
_nodeEditorService.ExecuteCommand(NodeScriptViewModel.NodeScript, new DeleteNode(NodeScriptViewModel.NodeScript, Node)); _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);
}
} }

View File

@ -30,7 +30,7 @@ public abstract class PinCollectionViewModel : ActivatableViewModelBase
.Subscribe(e => PinViewModels.Add(CreatePinViewModel(e.EventArgs.Value))) .Subscribe(e => PinViewModels.Add(CreatePinViewModel(e.EventArgs.Value)))
.DisposeWith(d); .DisposeWith(d);
Observable.FromEventPattern<SingleValueEventArgs<IPin>>(x => PinCollection.PinRemoved += x, x => PinCollection.PinRemoved -= x) Observable.FromEventPattern<SingleValueEventArgs<IPin>>(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); .DisposeWith(d);
}); });

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using System.Reactive; using System.Reactive;
using System.Reactive.Linq; using System.Reactive.Linq;
using Artemis.Core; using Artemis.Core;
@ -38,7 +39,7 @@ public abstract class PinViewModel : ActivatableViewModelBase
.Subscribe(e => connectedPins.Add(e.EventArgs.Value)) .Subscribe(e => connectedPins.Add(e.EventArgs.Value))
.DisposeWith(d); .DisposeWith(d);
Observable.FromEventPattern<SingleValueEventArgs<IPin>>(x => Pin.PinDisconnected += x, x => Pin.PinDisconnected -= x) Observable.FromEventPattern<SingleValueEventArgs<IPin>>(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); .DisposeWith(d);
Pin.WhenAnyValue(p => p.Type).Subscribe(_ => UpdatePinColor()).DisposeWith(d); Pin.WhenAnyValue(p => p.Type).Subscribe(_ => UpdatePinColor()).DisposeWith(d);
}); });

View File

@ -8,7 +8,7 @@ public class StringRegexMatchNode : Node
{ {
private string? _lastPattern; private string? _lastPattern;
private Regex? _regex; private Regex? _regex;
private bool _broken; private Exception? _exception;
public StringRegexMatchNode() : base("Regex Match", "Checks provided regex pattern matches the input.") 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) if (Input.Value == null || Pattern.Value == null)
return; 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) if (_regex == null || _lastPattern != Pattern.Value)
{ {
try try
{ {
_regex = new Regex(Pattern.Value, RegexOptions.Compiled); _regex = new Regex(Pattern.Value, RegexOptions.Compiled);
_broken = false; _exception = null;
} }
catch (Exception) catch (Exception e)
{ {
_broken = true; // If there is an exception, save it to keep rethrowing until the regex is fixed
return; _exception = e;
throw;
} }
finally finally
{ {