mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Merge branch 'feature/updating' into feature/gh-actions
This commit is contained in:
commit
f97d328f32
@ -35,15 +35,13 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="DryIoc.dll" Version="5.3.1" />
|
||||||
<PackageReference Include="EmbedIO" Version="3.5.0" />
|
<PackageReference Include="EmbedIO" Version="3.5.0" />
|
||||||
<PackageReference Include="HidSharp" Version="2.1.0" />
|
<PackageReference Include="HidSharp" Version="2.1.0" />
|
||||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||||
<PackageReference Include="LiteDB" Version="5.0.12" />
|
<PackageReference Include="LiteDB" Version="5.0.12" />
|
||||||
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
|
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="Ninject" Version="3.3.6" />
|
|
||||||
<PackageReference Include="Ninject.Extensions.ChildKernel" Version="3.3.0" />
|
|
||||||
<PackageReference Include="Ninject.Extensions.Conventions" Version="3.3.0" />
|
|
||||||
<PackageReference Include="RGB.NET.Core" Version="1.0.0" />
|
<PackageReference Include="RGB.NET.Core" Version="1.0.0" />
|
||||||
<PackageReference Include="RGB.NET.Layout" Version="1.0.0" />
|
<PackageReference Include="RGB.NET.Layout" Version="1.0.0" />
|
||||||
<PackageReference Include="RGB.NET.Presets" Version="1.0.0" />
|
<PackageReference Include="RGB.NET.Presets" Version="1.0.0" />
|
||||||
|
|||||||
66
src/Artemis.Core/DryIoc/ContainerExtensions.cs
Normal file
66
src/Artemis.Core/DryIoc/ContainerExtensions.cs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Artemis.Core.DryIoc.Factories;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.Storage;
|
||||||
|
using Artemis.Storage.Migrations.Interfaces;
|
||||||
|
using Artemis.Storage.Repositories.Interfaces;
|
||||||
|
using DryIoc;
|
||||||
|
|
||||||
|
namespace Artemis.Core.DryIoc;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides an extension method to register services onto a DryIoc <see cref="IContainer"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class CoreContainerExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Registers core services into the container.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="container">The builder building the current container</param>
|
||||||
|
public static void RegisterCore(this IContainer container)
|
||||||
|
{
|
||||||
|
Assembly[] coreAssembly = {typeof(IArtemisService).Assembly};
|
||||||
|
Assembly[] storageAssembly = {typeof(IRepository).Assembly};
|
||||||
|
|
||||||
|
// Bind all services as singletons
|
||||||
|
container.RegisterMany(coreAssembly, type => type.IsAssignableTo<IArtemisService>(), Reuse.Singleton);
|
||||||
|
container.RegisterMany(coreAssembly, type => type.IsAssignableTo<IProtectedArtemisService>(), Reuse.Singleton, setup: Setup.With(condition: HasAccessToProtectedService));
|
||||||
|
|
||||||
|
// Bind storage
|
||||||
|
container.RegisterDelegate(() => StorageManager.CreateRepository(Constants.DataFolder), Reuse.Singleton);
|
||||||
|
container.Register<StorageMigrationService>(Reuse.Singleton);
|
||||||
|
container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IRepository>(), Reuse.Singleton);
|
||||||
|
|
||||||
|
// Bind migrations
|
||||||
|
container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IStorageMigration>(), Reuse.Singleton, nonPublicServiceTypes: true);
|
||||||
|
|
||||||
|
container.Register<IPluginSettingsFactory, PluginSettingsFactory>(Reuse.Singleton);
|
||||||
|
container.Register(made: Made.Of(_ => ServiceInfo.Of<IPluginSettingsFactory>(), f => f.CreatePluginSettings(Arg.Index<Type>(0)), r => r.Parent.ImplementationType));
|
||||||
|
container.Register<ILoggerFactory, LoggerFactory>(Reuse.Singleton);
|
||||||
|
container.Register(made: Made.Of(_ => ServiceInfo.Of<ILoggerFactory>(), f => f.CreateLogger(Arg.Index<Type>(0)), r => r.Parent.ImplementationType));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers plugin services into the container, this is typically a child container.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="container">The builder building the current container</param>
|
||||||
|
/// <param name="plugin">The plugin to register</param>
|
||||||
|
public static void RegisterPlugin(this IContainer container, Plugin plugin)
|
||||||
|
{
|
||||||
|
container.RegisterInstance(plugin, setup: Setup.With(preventDisposal: true));
|
||||||
|
|
||||||
|
// Bind plugin service interfaces, DryIoc expects at least one match when calling RegisterMany so ensure there is something to register first
|
||||||
|
if (plugin.Assembly != null && plugin.Assembly.GetTypes().Any(t => t.IsAssignableTo<IPluginService>()))
|
||||||
|
container.RegisterMany(new[] {plugin.Assembly}, type => type.IsAssignableTo<IPluginService>(), Reuse.Singleton, ifAlreadyRegistered: IfAlreadyRegistered.Keep);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasAccessToProtectedService(Request request)
|
||||||
|
{
|
||||||
|
// Plugin assembly locations may not be set for some reason, that case it's also not allowed >:(
|
||||||
|
return request.Parent.ImplementationType != null &&
|
||||||
|
!string.IsNullOrWhiteSpace(request.Parent.ImplementationType.Assembly.Location) &&
|
||||||
|
!request.Parent.ImplementationType.Assembly.Location.StartsWith(Constants.PluginsFolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,17 +1,16 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Ninject.Activation;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Core;
|
using Serilog.Core;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace Artemis.Core.Ninject;
|
namespace Artemis.Core.DryIoc.Factories;
|
||||||
|
|
||||||
internal class LoggerProvider : Provider<ILogger>
|
internal class LoggerFactory : ILoggerFactory
|
||||||
{
|
{
|
||||||
internal static readonly LoggingLevelSwitch LoggingLevelSwitch = new(LogEventLevel.Verbose);
|
internal static readonly LoggingLevelSwitch LoggingLevelSwitch = new(LogEventLevel.Verbose);
|
||||||
|
|
||||||
private static readonly ILogger Logger = new LoggerConfiguration()
|
internal static readonly ILogger Logger = new LoggerConfiguration()
|
||||||
.Enrich.FromLogContext()
|
.Enrich.FromLogContext()
|
||||||
.WriteTo.File(Path.Combine(Constants.LogsFolder, "Artemis log-.log"),
|
.WriteTo.File(Path.Combine(Constants.LogsFolder, "Artemis log-.log"),
|
||||||
rollingInterval: RollingInterval.Day,
|
rollingInterval: RollingInterval.Day,
|
||||||
@ -24,12 +23,10 @@ internal class LoggerProvider : Provider<ILogger>
|
|||||||
.MinimumLevel.ControlledBy(LoggingLevelSwitch)
|
.MinimumLevel.ControlledBy(LoggingLevelSwitch)
|
||||||
.CreateLogger();
|
.CreateLogger();
|
||||||
|
|
||||||
protected override ILogger CreateInstance(IContext context)
|
/// <inheritdoc />
|
||||||
|
public ILogger CreateLogger(Type type)
|
||||||
{
|
{
|
||||||
Type? requestingType = context.Request.ParentContext?.Plan?.Type;
|
return Logger.ForContext(type);
|
||||||
if (requestingType != null)
|
|
||||||
return Logger.ForContext(requestingType);
|
|
||||||
return Logger;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,4 +37,9 @@ internal class ArtemisSink : ILogEventSink
|
|||||||
{
|
{
|
||||||
LogStore.Emit(logEvent);
|
LogStore.Emit(logEvent);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal interface ILoggerFactory
|
||||||
|
{
|
||||||
|
ILogger CreateLogger(Type type);
|
||||||
}
|
}
|
||||||
@ -1,35 +1,26 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.Storage.Repositories.Interfaces;
|
using Artemis.Storage.Repositories.Interfaces;
|
||||||
using Ninject.Activation;
|
|
||||||
|
|
||||||
namespace Artemis.Core.Ninject;
|
namespace Artemis.Core.DryIoc.Factories;
|
||||||
|
|
||||||
// TODO: Investigate if this can't just be set as a constant on the plugin child kernel
|
internal class PluginSettingsFactory : IPluginSettingsFactory
|
||||||
internal class PluginSettingsProvider : Provider<PluginSettings>
|
|
||||||
{
|
{
|
||||||
private static readonly List<PluginSettings> PluginSettings = new();
|
private static readonly List<PluginSettings> PluginSettings = new();
|
||||||
private readonly IPluginManagementService _pluginManagementService;
|
private readonly IPluginManagementService _pluginManagementService;
|
||||||
private readonly IPluginRepository _pluginRepository;
|
private readonly IPluginRepository _pluginRepository;
|
||||||
|
|
||||||
public PluginSettingsProvider(IPluginRepository pluginRepository, IPluginManagementService pluginManagementService)
|
public PluginSettingsFactory(IPluginRepository pluginRepository, IPluginManagementService pluginManagementService)
|
||||||
{
|
{
|
||||||
_pluginRepository = pluginRepository;
|
_pluginRepository = pluginRepository;
|
||||||
_pluginManagementService = pluginManagementService;
|
_pluginManagementService = pluginManagementService;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override PluginSettings CreateInstance(IContext context)
|
public PluginSettings CreatePluginSettings(Type type)
|
||||||
{
|
{
|
||||||
IRequest parentRequest = context.Request.ParentRequest;
|
Plugin? plugin = _pluginManagementService.GetPluginByAssembly(type.Assembly);
|
||||||
if (parentRequest == null)
|
|
||||||
throw new ArtemisCoreException("PluginSettings couldn't be injected, failed to get the injection parent request");
|
|
||||||
|
|
||||||
// First try by PluginInfo parameter
|
|
||||||
Plugin? plugin = parentRequest.Parameters.FirstOrDefault(p => p.Name == "Plugin")?.GetValue(context, null) as Plugin;
|
|
||||||
// Fall back to assembly based detection
|
|
||||||
if (plugin == null)
|
|
||||||
plugin = _pluginManagementService.GetPluginByAssembly(parentRequest.Service.Assembly);
|
|
||||||
|
|
||||||
if (plugin == null)
|
if (plugin == null)
|
||||||
throw new ArtemisCoreException("PluginSettings can only be injected with the PluginInfo parameter provided " +
|
throw new ArtemisCoreException("PluginSettings can only be injected with the PluginInfo parameter provided " +
|
||||||
@ -46,4 +37,9 @@ internal class PluginSettingsProvider : Provider<PluginSettings>
|
|||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal interface IPluginSettingsFactory
|
||||||
|
{
|
||||||
|
PluginSettings CreatePluginSettings(Type type);
|
||||||
}
|
}
|
||||||
20
src/Artemis.Core/Events/UpdateEventArgs.cs
Normal file
20
src/Artemis.Core/Events/UpdateEventArgs.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Artemis.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides data about application update events
|
||||||
|
/// </summary>
|
||||||
|
public class UpdateEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
internal UpdateEventArgs(bool silent)
|
||||||
|
{
|
||||||
|
Silent = silent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a boolean indicating whether to silently update or not.
|
||||||
|
/// </summary>
|
||||||
|
public bool Silent { get; }
|
||||||
|
}
|
||||||
@ -241,7 +241,16 @@ public class ColorGradient : IList<ColorGradientStop>, IList, INotifyCollectionC
|
|||||||
return _stops[^1].Color;
|
return _stops[^1].Color;
|
||||||
|
|
||||||
//find the first stop after the position
|
//find the first stop after the position
|
||||||
int stop2Index = _stops.FindIndex(s => s.Position >= position);
|
int stop2Index = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < _stops.Count; i++)
|
||||||
|
{
|
||||||
|
if (_stops[i].Position >= position)
|
||||||
|
{
|
||||||
|
stop2Index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
//if the position is before the first stop, return that color
|
//if the position is before the first stop, return that color
|
||||||
if (stop2Index == 0)
|
if (stop2Index == 0)
|
||||||
return _stops[0].Color;
|
return _stops[0].Color;
|
||||||
|
|||||||
@ -10,6 +10,11 @@ namespace Artemis.Core;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ProfileCategory : CorePropertyChanged, IStorageModel
|
public class ProfileCategory : CorePropertyChanged, IStorageModel
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an empty profile category.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly ProfileCategory Empty = new("Empty", -1);
|
||||||
|
|
||||||
private readonly List<ProfileConfiguration> _profileConfigurations = new();
|
private readonly List<ProfileConfiguration> _profileConfigurations = new();
|
||||||
private bool _isCollapsed;
|
private bool _isCollapsed;
|
||||||
private bool _isSuspended;
|
private bool _isSuspended;
|
||||||
|
|||||||
@ -16,7 +16,10 @@ public class Hotkey : CorePropertyChanged, IStorageModel
|
|||||||
Entity = new ProfileConfigurationHotkeyEntity();
|
Entity = new ProfileConfigurationHotkeyEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Hotkey(ProfileConfigurationHotkeyEntity entity)
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="Hotkey" /> based on the provided entity
|
||||||
|
/// </summary>
|
||||||
|
public Hotkey(ProfileConfigurationHotkeyEntity entity)
|
||||||
{
|
{
|
||||||
Entity = entity;
|
Entity = entity;
|
||||||
Load();
|
Load();
|
||||||
@ -32,7 +35,10 @@ public class Hotkey : CorePropertyChanged, IStorageModel
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public KeyboardModifierKey? Modifiers { get; set; }
|
public KeyboardModifierKey? Modifiers { get; set; }
|
||||||
|
|
||||||
internal ProfileConfigurationHotkeyEntity Entity { get; }
|
/// <summary>
|
||||||
|
/// Gets the entity used to store this hotkey
|
||||||
|
/// </summary>
|
||||||
|
public ProfileConfigurationHotkeyEntity Entity { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether the provided <see cref="ArtemisKeyboardKeyEventArgs" /> match the hotkey
|
/// Determines whether the provided <see cref="ArtemisKeyboardKeyEventArgs" /> match the hotkey
|
||||||
|
|||||||
@ -11,6 +11,11 @@ namespace Artemis.Core;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
|
public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an empty profile.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly ProfileConfiguration Empty = new(ProfileCategory.Empty, "Empty", "Empty");
|
||||||
|
|
||||||
private ActivationBehaviour _activationBehaviour;
|
private ActivationBehaviour _activationBehaviour;
|
||||||
private bool _activationConditionMet;
|
private bool _activationConditionMet;
|
||||||
private ProfileCategory _category;
|
private ProfileCategory _category;
|
||||||
|
|||||||
@ -1,83 +0,0 @@
|
|||||||
using Artemis.Core.Services;
|
|
||||||
using Artemis.Storage;
|
|
||||||
using Artemis.Storage.Migrations.Interfaces;
|
|
||||||
using Artemis.Storage.Repositories.Interfaces;
|
|
||||||
using LiteDB;
|
|
||||||
using Ninject.Activation;
|
|
||||||
using Ninject.Extensions.Conventions;
|
|
||||||
using Ninject.Modules;
|
|
||||||
using Ninject.Planning.Bindings.Resolvers;
|
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Artemis.Core.Ninject;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The main <see cref="NinjectModule" /> of the Artemis Core that binds all services
|
|
||||||
/// </summary>
|
|
||||||
public class CoreModule : NinjectModule
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Load()
|
|
||||||
{
|
|
||||||
if (Kernel == null)
|
|
||||||
throw new ArtemisCoreException("Failed to bind Ninject Core module, kernel is null.");
|
|
||||||
|
|
||||||
Kernel.Components.Remove<IMissingBindingResolver, SelfBindingResolver>();
|
|
||||||
|
|
||||||
// Bind all services as singletons
|
|
||||||
Kernel.Bind(x =>
|
|
||||||
{
|
|
||||||
x.FromThisAssembly()
|
|
||||||
.IncludingNonPublicTypes()
|
|
||||||
.SelectAllClasses()
|
|
||||||
.InheritedFrom<IArtemisService>()
|
|
||||||
.BindAllInterfaces()
|
|
||||||
.Configure(c => c.InSingletonScope());
|
|
||||||
});
|
|
||||||
|
|
||||||
// Bind all protected services as singletons
|
|
||||||
Kernel.Bind(x =>
|
|
||||||
{
|
|
||||||
x.FromThisAssembly()
|
|
||||||
.IncludingNonPublicTypes()
|
|
||||||
.SelectAllClasses()
|
|
||||||
.InheritedFrom<IProtectedArtemisService>()
|
|
||||||
.BindAllInterfaces()
|
|
||||||
.Configure(c => c.When(HasAccessToProtectedService).InSingletonScope());
|
|
||||||
});
|
|
||||||
|
|
||||||
Kernel.Bind<LiteRepository>().ToMethod(_ => StorageManager.CreateRepository(Constants.DataFolder)).InSingletonScope();
|
|
||||||
Kernel.Bind<StorageMigrationService>().ToSelf().InSingletonScope();
|
|
||||||
|
|
||||||
// Bind all migrations as singletons
|
|
||||||
Kernel.Bind(x =>
|
|
||||||
{
|
|
||||||
x.FromAssemblyContaining<IStorageMigration>()
|
|
||||||
.IncludingNonPublicTypes()
|
|
||||||
.SelectAllClasses()
|
|
||||||
.InheritedFrom<IStorageMigration>()
|
|
||||||
.BindAllInterfaces()
|
|
||||||
.Configure(c => c.InSingletonScope());
|
|
||||||
});
|
|
||||||
|
|
||||||
// Bind all repositories as singletons
|
|
||||||
Kernel.Bind(x =>
|
|
||||||
{
|
|
||||||
x.FromAssemblyContaining<IRepository>()
|
|
||||||
.IncludingNonPublicTypes()
|
|
||||||
.SelectAllClasses()
|
|
||||||
.InheritedFrom<IRepository>()
|
|
||||||
.BindAllInterfaces()
|
|
||||||
.Configure(c => c.InSingletonScope());
|
|
||||||
});
|
|
||||||
|
|
||||||
Kernel.Bind<PluginSettings>().ToProvider<PluginSettingsProvider>();
|
|
||||||
Kernel.Bind<ILogger>().ToProvider<LoggerProvider>();
|
|
||||||
Kernel.Bind<LoggerProvider>().ToSelf();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool HasAccessToProtectedService(IRequest r)
|
|
||||||
{
|
|
||||||
return r.ParentRequest != null && !r.ParentRequest.Service.Assembly.Location.StartsWith(Constants.PluginsFolder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Artemis.Core.Services;
|
|
||||||
using Ninject.Extensions.Conventions;
|
|
||||||
using Ninject.Modules;
|
|
||||||
using Ninject.Planning.Bindings.Resolvers;
|
|
||||||
|
|
||||||
namespace Artemis.Core.Ninject;
|
|
||||||
|
|
||||||
internal class PluginModule : NinjectModule
|
|
||||||
{
|
|
||||||
public PluginModule(Plugin plugin)
|
|
||||||
{
|
|
||||||
Plugin = plugin ?? throw new ArgumentNullException(nameof(plugin));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Plugin Plugin { get; }
|
|
||||||
|
|
||||||
public override void Load()
|
|
||||||
{
|
|
||||||
if (Kernel == null)
|
|
||||||
throw new ArtemisCoreException("Failed to bind plugin child module, kernel is null.");
|
|
||||||
|
|
||||||
Kernel.Components.Remove<IMissingBindingResolver, SelfBindingResolver>();
|
|
||||||
|
|
||||||
Kernel.Bind<Plugin>().ToConstant(Plugin).InTransientScope();
|
|
||||||
|
|
||||||
// Bind plugin service interfaces
|
|
||||||
Kernel.Bind(x =>
|
|
||||||
{
|
|
||||||
x.From(Plugin.Assembly)
|
|
||||||
.IncludingNonPublicTypes()
|
|
||||||
.SelectAllClasses()
|
|
||||||
.InheritedFrom<IPluginService>()
|
|
||||||
.BindAllInterfaces()
|
|
||||||
.Configure(c => c.InSingletonScope());
|
|
||||||
});
|
|
||||||
|
|
||||||
// Plugin developers may not use an interface so bind the plugin services to themselves
|
|
||||||
// Sadly if they do both, the kernel will treat the interface and the base type as two different singletons
|
|
||||||
// perhaps we can avoid that, but I'm not sure how
|
|
||||||
Kernel.Bind(x =>
|
|
||||||
{
|
|
||||||
x.From(Plugin.Assembly)
|
|
||||||
.IncludingNonPublicTypes()
|
|
||||||
.SelectAllClasses()
|
|
||||||
.InheritedFrom<IPluginService>()
|
|
||||||
.BindToSelf()
|
|
||||||
.Configure(c => c.InSingletonScope());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
using Artemis.Core.Services;
|
|
||||||
using Ninject;
|
|
||||||
using Ninject.Activation;
|
|
||||||
|
|
||||||
namespace Artemis.Core.Ninject;
|
|
||||||
|
|
||||||
internal class SettingsServiceProvider : Provider<ISettingsService>
|
|
||||||
{
|
|
||||||
private readonly SettingsService _instance;
|
|
||||||
|
|
||||||
public SettingsServiceProvider(IKernel kernel)
|
|
||||||
{
|
|
||||||
// This is not lazy, but the core is always going to be using this anyway
|
|
||||||
_instance = kernel.Get<SettingsService>();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override ISettingsService CreateInstance(IContext context)
|
|
||||||
{
|
|
||||||
IRequest parentRequest = context.Request.ParentRequest;
|
|
||||||
if (parentRequest == null || typeof(PluginFeature).IsAssignableFrom(parentRequest.Service))
|
|
||||||
throw new ArtemisPluginException($"SettingsService can not be injected into a plugin. Inject {nameof(PluginSettings)} instead.");
|
|
||||||
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,9 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Ninject;
|
|
||||||
using RGB.NET.Core;
|
using RGB.NET.Core;
|
||||||
using Serilog;
|
|
||||||
|
|
||||||
namespace Artemis.Core.DeviceProviders;
|
namespace Artemis.Core.DeviceProviders;
|
||||||
|
|
||||||
@ -26,14 +24,7 @@ public abstract class DeviceProvider : PluginFeature
|
|||||||
/// The RGB.NET device provider backing this Artemis device provider
|
/// The RGB.NET device provider backing this Artemis device provider
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IRGBDeviceProvider RgbDeviceProvider { get; }
|
public IRGBDeviceProvider RgbDeviceProvider { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// TODO: Make internal while still injecting.
|
|
||||||
/// A logger used by the device provider internally, ignore this
|
|
||||||
/// </summary>
|
|
||||||
[Inject]
|
|
||||||
public ILogger? Logger { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A boolean indicating whether this device provider detects the physical layout of connected keyboards.
|
/// A boolean indicating whether this device provider detects the physical layout of connected keyboards.
|
||||||
/// <para>
|
/// <para>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using Artemis.Storage.Entities.Profile;
|
using Artemis.Storage.Entities.Profile;
|
||||||
using Ninject;
|
|
||||||
|
|
||||||
namespace Artemis.Core.LayerBrushes;
|
namespace Artemis.Core.LayerBrushes;
|
||||||
|
|
||||||
@ -63,7 +62,7 @@ public class LayerBrushDescriptor
|
|||||||
if (layer == null)
|
if (layer == null)
|
||||||
throw new ArgumentNullException(nameof(layer));
|
throw new ArgumentNullException(nameof(layer));
|
||||||
|
|
||||||
BaseLayerBrush brush = (BaseLayerBrush) Provider.Plugin.Kernel!.Get(LayerBrushType);
|
BaseLayerBrush brush = (BaseLayerBrush) Provider.Plugin.Resolve(LayerBrushType);
|
||||||
brush.Layer = layer;
|
brush.Layer = layer;
|
||||||
brush.Descriptor = this;
|
brush.Descriptor = this;
|
||||||
brush.LayerBrushEntity = entity ?? new LayerBrushEntity {ProviderId = Provider.Id, BrushType = LayerBrushType.FullName};
|
brush.LayerBrushEntity = entity ?? new LayerBrushEntity {ProviderId = Provider.Id, BrushType = LayerBrushType.FullName};
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using Artemis.Core.LayerEffects.Placeholder;
|
using Artemis.Core.LayerEffects.Placeholder;
|
||||||
using Artemis.Storage.Entities.Profile;
|
using Artemis.Storage.Entities.Profile;
|
||||||
using Ninject;
|
|
||||||
|
|
||||||
namespace Artemis.Core.LayerEffects;
|
namespace Artemis.Core.LayerEffects;
|
||||||
|
|
||||||
@ -80,7 +79,7 @@ public class LayerEffectDescriptor
|
|||||||
if (LayerEffectType == null)
|
if (LayerEffectType == null)
|
||||||
throw new ArtemisCoreException("Cannot create an instance of a layer effect because this descriptor is not a placeholder but is still missing its LayerEffectType");
|
throw new ArtemisCoreException("Cannot create an instance of a layer effect because this descriptor is not a placeholder but is still missing its LayerEffectType");
|
||||||
|
|
||||||
BaseLayerEffect effect = (BaseLayerEffect) Provider.Plugin.Kernel!.Get(LayerEffectType);
|
BaseLayerEffect effect = (BaseLayerEffect) Provider.Plugin.Resolve(LayerEffectType);
|
||||||
effect.ProfileElement = renderElement;
|
effect.ProfileElement = renderElement;
|
||||||
effect.Descriptor = this;
|
effect.Descriptor = this;
|
||||||
if (entity != null)
|
if (entity != null)
|
||||||
|
|||||||
@ -7,8 +7,8 @@ using System.Linq;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Artemis.Core.DeviceProviders;
|
using Artemis.Core.DeviceProviders;
|
||||||
using Artemis.Storage.Entities.Plugins;
|
using Artemis.Storage.Entities.Plugins;
|
||||||
|
using DryIoc;
|
||||||
using McMaster.NETCore.Plugins;
|
using McMaster.NETCore.Plugins;
|
||||||
using Ninject;
|
|
||||||
|
|
||||||
namespace Artemis.Core;
|
namespace Artemis.Core;
|
||||||
|
|
||||||
@ -88,9 +88,9 @@ public class Plugin : CorePropertyChanged, IDisposable
|
|||||||
public PluginBootstrapper? Bootstrapper { get; internal set; }
|
public PluginBootstrapper? Bootstrapper { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Ninject kernel of the plugin
|
/// Gets the IOC container of the plugin, only use this for advanced IOC operations, otherwise see <see cref="Resolve"/> and <see cref="Resolve{T}"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IKernel? Kernel { get; internal set; }
|
public IContainer? Container { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The PluginLoader backing this plugin
|
/// The PluginLoader backing this plugin
|
||||||
@ -165,16 +165,66 @@ public class Plugin : CorePropertyChanged, IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an instance of the specified service using the plugins dependency injection container.
|
/// Gets an instance of the specified service using the plugins dependency injection container.
|
||||||
/// <para>Note: To use parameters reference Ninject and use <see cref="Kernel" /> directly.</para>
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="arguments">Arguments to supply to the service.</param>
|
||||||
/// <typeparam name="T">The service to resolve.</typeparam>
|
/// <typeparam name="T">The service to resolve.</typeparam>
|
||||||
/// <returns>An instance of the service.</returns>
|
/// <returns>An instance of the service.</returns>
|
||||||
public T Get<T>()
|
/// <seealso cref="Resolve"/>
|
||||||
|
public T Resolve<T>(params object?[] arguments)
|
||||||
{
|
{
|
||||||
if (Kernel == null)
|
if (Container == null)
|
||||||
throw new ArtemisPluginException("Cannot use Get<T> before the plugin finished loading");
|
throw new ArtemisPluginException("Cannot use Resolve<T> before the plugin finished loading");
|
||||||
return Kernel.Get<T>();
|
return Container.Resolve<T>(args: arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instance of the specified service using the plugins dependency injection container.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type of service to resolve.</param>
|
||||||
|
/// <param name="arguments">Arguments to supply to the service.</param>
|
||||||
|
/// <returns>An instance of the service.</returns>
|
||||||
|
/// <seealso cref="Resolve{T}"/>
|
||||||
|
public object Resolve(Type type, params object?[] arguments)
|
||||||
|
{
|
||||||
|
if (Container == null)
|
||||||
|
throw new ArtemisPluginException("Cannot use Resolve before the plugin finished loading");
|
||||||
|
return Container.Resolve(type, args: arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers service of <typeparamref name="TService" /> type implemented by <typeparamref name="TImplementation" /> type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scope">The scope in which the service should live, if you are not sure leave it on singleton.</param>
|
||||||
|
/// <typeparam name="TService">The service to register.</typeparam>
|
||||||
|
/// <typeparam name="TImplementation">The implementation of the service to register.</typeparam>
|
||||||
|
public void Register<TService, TImplementation>(PluginServiceScope scope = PluginServiceScope.Singleton) where TImplementation : TService
|
||||||
|
{
|
||||||
|
IReuse reuse = scope switch
|
||||||
|
{
|
||||||
|
PluginServiceScope.Transient => Reuse.Transient,
|
||||||
|
PluginServiceScope.Singleton => Reuse.Singleton,
|
||||||
|
PluginServiceScope.Scoped => Reuse.Scoped,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(scope), scope, null)
|
||||||
|
};
|
||||||
|
Container.Register<TService, TImplementation>(reuse);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers implementation type <typeparamref name="TImplementation" /> with itself as service type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scope">The scope in which the service should live, if you are not sure leave it on singleton.</param>
|
||||||
|
/// <typeparam name="TImplementation">The implementation of the service to register.</typeparam>
|
||||||
|
public void Register<TImplementation>(PluginServiceScope scope = PluginServiceScope.Singleton)
|
||||||
|
{
|
||||||
|
IReuse reuse = scope switch
|
||||||
|
{
|
||||||
|
PluginServiceScope.Transient => Reuse.Transient,
|
||||||
|
PluginServiceScope.Singleton => Reuse.Singleton,
|
||||||
|
PluginServiceScope.Scoped => Reuse.Scoped,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(scope), scope, null)
|
||||||
|
};
|
||||||
|
Container.Register<TImplementation>(reuse);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -218,7 +268,7 @@ public class Plugin : CorePropertyChanged, IDisposable
|
|||||||
feature.Instance?.Dispose();
|
feature.Instance?.Dispose();
|
||||||
SetEnabled(false);
|
SetEnabled(false);
|
||||||
|
|
||||||
Kernel?.Dispose();
|
Container?.Dispose();
|
||||||
PluginLoader?.Dispose();
|
PluginLoader?.Dispose();
|
||||||
|
|
||||||
GC.Collect();
|
GC.Collect();
|
||||||
@ -340,4 +390,26 @@ public class Plugin : CorePropertyChanged, IDisposable
|
|||||||
Dispose(true);
|
Dispose(true);
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a scope in which a plugin service is injected by the IOC container.
|
||||||
|
/// </summary>
|
||||||
|
public enum PluginServiceScope
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Services in this scope are never reused, a new instance is injected each time.
|
||||||
|
/// </summary>
|
||||||
|
Transient,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Services in this scope are reused for as long as the plugin lives, the same instance is injected each time.
|
||||||
|
/// </summary>
|
||||||
|
Singleton,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Services in this scope are reused within a container scope, this is an advanced setting you shouldn't need.
|
||||||
|
/// <para>To learn more see <a href="https://github.com/dadhi/DryIoc/blob/master/docs/DryIoc.Docs/ReuseAndScopes.md#reusescoped">the DryIoc docs</a>.</para>
|
||||||
|
/// </summary>
|
||||||
|
Scoped
|
||||||
}
|
}
|
||||||
@ -1,10 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Artemis.Core.DeviceProviders;
|
|
||||||
using Artemis.Core.LayerBrushes;
|
|
||||||
using Artemis.Core.LayerEffects;
|
|
||||||
using Artemis.Core.Modules;
|
|
||||||
using Artemis.Storage.Entities.Plugins;
|
using Artemis.Storage.Entities.Plugins;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Timers;
|
using System.Timers;
|
||||||
|
using Artemis.Core.DryIoc.Factories;
|
||||||
using Artemis.Core.Modules;
|
using Artemis.Core.Modules;
|
||||||
using Artemis.Core.Services;
|
|
||||||
using Ninject;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Artemis.Core;
|
namespace Artemis.Core;
|
||||||
@ -11,7 +10,7 @@ namespace Artemis.Core;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a registration for a timed plugin update
|
/// Represents a registration for a timed plugin update
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TimedUpdateRegistration : IDisposable
|
public sealed class TimedUpdateRegistration : IDisposable
|
||||||
{
|
{
|
||||||
private readonly object _lock = new();
|
private readonly object _lock = new();
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
@ -21,9 +20,7 @@ public class TimedUpdateRegistration : IDisposable
|
|||||||
|
|
||||||
internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Action<double> action, string? name)
|
internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Action<double> action, string? name)
|
||||||
{
|
{
|
||||||
if (CoreService.Kernel == null)
|
_logger = LoggerFactory.Logger.ForContext<TimedUpdateRegistration>();
|
||||||
throw new ArtemisCoreException("Cannot create a TimedUpdateRegistration before initializing the Core");
|
|
||||||
_logger = CoreService.Kernel.Get<ILogger>();
|
|
||||||
|
|
||||||
Feature = feature;
|
Feature = feature;
|
||||||
Interval = interval;
|
Interval = interval;
|
||||||
@ -38,9 +35,7 @@ public class TimedUpdateRegistration : IDisposable
|
|||||||
|
|
||||||
internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Func<double, Task> asyncAction, string? name)
|
internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Func<double, Task> asyncAction, string? name)
|
||||||
{
|
{
|
||||||
if (CoreService.Kernel == null)
|
_logger = LoggerFactory.Logger.ForContext<TimedUpdateRegistration>();
|
||||||
throw new ArtemisCoreException("Cannot create a TimedUpdateRegistration before initializing the Core");
|
|
||||||
_logger = CoreService.Kernel.Get<ILogger>();
|
|
||||||
|
|
||||||
Feature = feature;
|
Feature = feature;
|
||||||
Interval = interval;
|
Interval = interval;
|
||||||
@ -125,33 +120,13 @@ public class TimedUpdateRegistration : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public sealed override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
if (Interval.TotalSeconds >= 1)
|
if (Interval.TotalSeconds >= 1)
|
||||||
return $"{Name} ({Interval.TotalSeconds} sec)";
|
return $"{Name} ({Interval.TotalSeconds} sec)";
|
||||||
return $"{Name} ({Interval.TotalMilliseconds} ms)";
|
return $"{Name} ({Interval.TotalMilliseconds} ms)";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="disposing">
|
|
||||||
/// <see langword="true" /> to release both managed and unmanaged resources;
|
|
||||||
/// <see langword="false" /> to release only unmanaged resources.
|
|
||||||
/// </param>
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
|
|
||||||
Feature.Enabled -= FeatureOnEnabled;
|
|
||||||
Feature.Disabled -= FeatureOnDisabled;
|
|
||||||
|
|
||||||
_disposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TimerOnElapsed(object? sender, ElapsedEventArgs e)
|
private void TimerOnElapsed(object? sender, ElapsedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!Feature.IsEnabled)
|
if (!Feature.IsEnabled)
|
||||||
@ -204,9 +179,12 @@ public class TimedUpdateRegistration : IDisposable
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Dispose(true);
|
Stop();
|
||||||
Feature.Profiler.ClearMeasurements(ToString());
|
|
||||||
|
|
||||||
GC.SuppressFinalize(this);
|
Feature.Enabled -= FeatureOnEnabled;
|
||||||
|
Feature.Disabled -= FeatureOnDisabled;
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
Feature.Profiler.ClearMeasurements(ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3,11 +3,11 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Artemis.Core.Ninject;
|
using Artemis.Core.DryIoc.Factories;
|
||||||
using Artemis.Core.ScriptingProviders;
|
using Artemis.Core.ScriptingProviders;
|
||||||
using Artemis.Storage;
|
using Artemis.Storage;
|
||||||
|
using DryIoc;
|
||||||
using HidSharp;
|
using HidSharp;
|
||||||
using Ninject;
|
|
||||||
using RGB.NET.Core;
|
using RGB.NET.Core;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
@ -20,7 +20,6 @@ namespace Artemis.Core.Services;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal class CoreService : ICoreService
|
internal class CoreService : ICoreService
|
||||||
{
|
{
|
||||||
internal static IKernel? Kernel;
|
|
||||||
private readonly Stopwatch _frameStopWatch;
|
private readonly Stopwatch _frameStopWatch;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly PluginSetting<LogEventLevel> _loggingLevel;
|
private readonly PluginSetting<LogEventLevel> _loggingLevel;
|
||||||
@ -36,7 +35,7 @@ internal class CoreService : ICoreService
|
|||||||
private DateTime _lastFrameRateSample;
|
private DateTime _lastFrameRateSample;
|
||||||
|
|
||||||
// ReSharper disable UnusedParameter.Local
|
// ReSharper disable UnusedParameter.Local
|
||||||
public CoreService(IKernel kernel,
|
public CoreService(IContainer container,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
StorageMigrationService _1, // injected to ensure migration runs early
|
StorageMigrationService _1, // injected to ensure migration runs early
|
||||||
ISettingsService settingsService,
|
ISettingsService settingsService,
|
||||||
@ -47,8 +46,7 @@ internal class CoreService : ICoreService
|
|||||||
IScriptingService scriptingService,
|
IScriptingService scriptingService,
|
||||||
IProcessMonitorService _2)
|
IProcessMonitorService _2)
|
||||||
{
|
{
|
||||||
Kernel = kernel;
|
Constants.CorePlugin.Container = container;
|
||||||
Constants.CorePlugin.Kernel = kernel;
|
|
||||||
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_pluginManagementService = pluginManagementService;
|
_pluginManagementService = pluginManagementService;
|
||||||
@ -85,19 +83,19 @@ internal class CoreService : ICoreService
|
|||||||
if (parts.Length == 2 && Enum.TryParse(typeof(LogEventLevel), parts[1], true, out object? logLevelArgument))
|
if (parts.Length == 2 && Enum.TryParse(typeof(LogEventLevel), parts[1], true, out object? logLevelArgument))
|
||||||
{
|
{
|
||||||
_logger.Information("Setting logging level to {loggingLevel} from startup argument", (LogEventLevel) logLevelArgument!);
|
_logger.Information("Setting logging level to {loggingLevel} from startup argument", (LogEventLevel) logLevelArgument!);
|
||||||
LoggerProvider.LoggingLevelSwitch.MinimumLevel = (LogEventLevel) logLevelArgument;
|
LoggerFactory.LoggingLevelSwitch.MinimumLevel = (LogEventLevel) logLevelArgument;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Warning("Failed to set log level from startup argument {argument}", argument);
|
_logger.Warning("Failed to set log level from startup argument {argument}", argument);
|
||||||
_logger.Information("Setting logging level to {loggingLevel}", _loggingLevel.Value);
|
_logger.Information("Setting logging level to {loggingLevel}", _loggingLevel.Value);
|
||||||
LoggerProvider.LoggingLevelSwitch.MinimumLevel = _loggingLevel.Value;
|
LoggerFactory.LoggingLevelSwitch.MinimumLevel = _loggingLevel.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Information("Setting logging level to {loggingLevel}", _loggingLevel.Value);
|
_logger.Information("Setting logging level to {loggingLevel}", _loggingLevel.Value);
|
||||||
LoggerProvider.LoggingLevelSwitch.MinimumLevel = _loggingLevel.Value;
|
LoggerFactory.LoggingLevelSwitch.MinimumLevel = _loggingLevel.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,12 +193,6 @@ internal class CoreService : ICoreService
|
|||||||
public bool ProfileRenderingDisabled { get; set; }
|
public bool ProfileRenderingDisabled { get; set; }
|
||||||
public bool IsElevated { get; set; }
|
public bool IsElevated { get; set; }
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
// Dispose services
|
|
||||||
_pluginManagementService.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsInitialized { get; set; }
|
public bool IsInitialized { get; set; }
|
||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
|
|||||||
@ -5,7 +5,7 @@ namespace Artemis.Core.Services;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A service that initializes the Core and manages the render loop
|
/// A service that initializes the Core and manages the render loop
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ICoreService : IArtemisService, IDisposable
|
public interface ICoreService : IArtemisService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets whether the or not the core has been initialized
|
/// Gets whether the or not the core has been initialized
|
||||||
|
|||||||
@ -5,8 +5,8 @@ using System.Reflection;
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Artemis.Storage.Entities.Profile.Nodes;
|
using Artemis.Storage.Entities.Profile.Nodes;
|
||||||
|
using DryIoc;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Ninject;
|
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Artemis.Core.Services;
|
namespace Artemis.Core.Services;
|
||||||
@ -15,17 +15,17 @@ internal class NodeService : INodeService
|
|||||||
{
|
{
|
||||||
#region Constants
|
#region Constants
|
||||||
|
|
||||||
private static readonly Type TYPE_NODE = typeof(INode);
|
private static readonly Type TypeNode = typeof(INode);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private readonly IKernel _kernel;
|
private readonly IContainer _container;
|
||||||
|
|
||||||
#region Constructors
|
#region Constructors
|
||||||
|
|
||||||
public NodeService(IKernel kernel)
|
public NodeService(IContainer container)
|
||||||
{
|
{
|
||||||
_kernel = kernel;
|
_container = container;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -69,7 +69,7 @@ internal class NodeService : INodeService
|
|||||||
if (plugin == null) throw new ArgumentNullException(nameof(plugin));
|
if (plugin == null) throw new ArgumentNullException(nameof(plugin));
|
||||||
if (nodeType == null) throw new ArgumentNullException(nameof(nodeType));
|
if (nodeType == null) throw new ArgumentNullException(nameof(nodeType));
|
||||||
|
|
||||||
if (!TYPE_NODE.IsAssignableFrom(nodeType)) throw new ArgumentException("Node has to be a base type of the Node-Type.", nameof(nodeType));
|
if (!TypeNode.IsAssignableFrom(nodeType)) throw new ArgumentException("Node has to be a base type of the Node-Type.", nameof(nodeType));
|
||||||
|
|
||||||
NodeAttribute? nodeAttribute = nodeType.GetCustomAttribute<NodeAttribute>();
|
NodeAttribute? nodeAttribute = nodeType.GetCustomAttribute<NodeAttribute>();
|
||||||
string name = nodeAttribute?.Name ?? nodeType.Name;
|
string name = nodeAttribute?.Name ?? nodeType.Name;
|
||||||
@ -106,8 +106,10 @@ internal class NodeService : INodeService
|
|||||||
|
|
||||||
private INode CreateNode(INodeScript script, NodeEntity? entity, Type nodeType)
|
private INode CreateNode(INodeScript script, NodeEntity? entity, Type nodeType)
|
||||||
{
|
{
|
||||||
INode node = _kernel.Get(nodeType) as INode ?? throw new InvalidOperationException($"Node {nodeType} is not an INode");
|
INode node = _container.Resolve(nodeType) as INode ?? throw new InvalidOperationException($"Node {nodeType} is not an INode");
|
||||||
|
if (node is Node concreteNode)
|
||||||
|
concreteNode.Container = _container;
|
||||||
|
|
||||||
if (entity != null)
|
if (entity != null)
|
||||||
{
|
{
|
||||||
node.X = entity.X;
|
node.X = entity.X;
|
||||||
|
|||||||
@ -5,19 +5,15 @@ using System.IO;
|
|||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.Loader;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.Core.DeviceProviders;
|
using Artemis.Core.DeviceProviders;
|
||||||
using Artemis.Core.Ninject;
|
using Artemis.Core.DryIoc;
|
||||||
using Artemis.Storage.Entities.General;
|
using Artemis.Storage.Entities.General;
|
||||||
using Artemis.Storage.Entities.Plugins;
|
using Artemis.Storage.Entities.Plugins;
|
||||||
using Artemis.Storage.Entities.Surface;
|
using Artemis.Storage.Entities.Surface;
|
||||||
using Artemis.Storage.Repositories.Interfaces;
|
using Artemis.Storage.Repositories.Interfaces;
|
||||||
|
using DryIoc;
|
||||||
using McMaster.NETCore.Plugins;
|
using McMaster.NETCore.Plugins;
|
||||||
using Ninject;
|
|
||||||
using Ninject.Extensions.ChildKernel;
|
|
||||||
using Ninject.Parameters;
|
|
||||||
using Ninject.Planning.Bindings.Resolvers;
|
|
||||||
using RGB.NET.Core;
|
using RGB.NET.Core;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
@ -29,7 +25,7 @@ namespace Artemis.Core.Services;
|
|||||||
internal class PluginManagementService : IPluginManagementService
|
internal class PluginManagementService : IPluginManagementService
|
||||||
{
|
{
|
||||||
private readonly IDeviceRepository _deviceRepository;
|
private readonly IDeviceRepository _deviceRepository;
|
||||||
private readonly IKernel _kernel;
|
private readonly IContainer _container;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IPluginRepository _pluginRepository;
|
private readonly IPluginRepository _pluginRepository;
|
||||||
private readonly List<Plugin> _plugins;
|
private readonly List<Plugin> _plugins;
|
||||||
@ -37,9 +33,9 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
private bool _isElevated;
|
private bool _isElevated;
|
||||||
|
|
||||||
public PluginManagementService(IKernel kernel, ILogger logger, IPluginRepository pluginRepository, IDeviceRepository deviceRepository, IQueuedActionRepository queuedActionRepository)
|
public PluginManagementService(IContainer container, ILogger logger, IPluginRepository pluginRepository, IDeviceRepository deviceRepository, IQueuedActionRepository queuedActionRepository)
|
||||||
{
|
{
|
||||||
_kernel = kernel;
|
_container = container;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_pluginRepository = pluginRepository;
|
_pluginRepository = pluginRepository;
|
||||||
_deviceRepository = deviceRepository;
|
_deviceRepository = deviceRepository;
|
||||||
@ -88,7 +84,20 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
|
|
||||||
foreach (FileInfo zipFile in builtInPluginDirectory.EnumerateFiles("*.zip"))
|
foreach (FileInfo zipFile in builtInPluginDirectory.EnumerateFiles("*.zip"))
|
||||||
{
|
{
|
||||||
// Find the metadata file in the zip
|
try
|
||||||
|
{
|
||||||
|
ExtractBuiltInPlugin(zipFile, pluginDirectory);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.Error(e, "Failed to copy built-in plugin from {ZipFile}", zipFile.FullName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExtractBuiltInPlugin(FileInfo zipFile, DirectoryInfo pluginDirectory)
|
||||||
|
{
|
||||||
|
// Find the metadata file in the zip
|
||||||
using ZipArchive archive = ZipFile.OpenRead(zipFile.FullName);
|
using ZipArchive archive = ZipFile.OpenRead(zipFile.FullName);
|
||||||
ZipArchiveEntry? metaDataFileEntry = archive.GetEntry("plugin.json");
|
ZipArchiveEntry? metaDataFileEntry = archive.GetEntry("plugin.json");
|
||||||
if (metaDataFileEntry == null)
|
if (metaDataFileEntry == null)
|
||||||
@ -139,7 +148,6 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -198,6 +206,10 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
// Disposal happens manually before container disposal but the container doesn't know that so a 2nd call will be made
|
||||||
|
if (_disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
UnloadPlugins();
|
UnloadPlugins();
|
||||||
}
|
}
|
||||||
@ -340,11 +352,11 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
configure.IsUnloadable = true;
|
configure.IsUnloadable = true;
|
||||||
configure.LoadInMemory = true;
|
configure.LoadInMemory = true;
|
||||||
configure.PreferSharedTypes = true;
|
configure.PreferSharedTypes = true;
|
||||||
|
|
||||||
// Resolving failed, try a loaded assembly but ignoring the version
|
// Resolving failed, try a loaded assembly but ignoring the version
|
||||||
configure.DefaultContext.Resolving += (context, assemblyName) => context.Assemblies.FirstOrDefault(a => a.GetName().Name == assemblyName.Name);
|
configure.DefaultContext.Resolving += (context, assemblyName) => context.Assemblies.FirstOrDefault(a => a.GetName().Name == assemblyName.Name);
|
||||||
});
|
});
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
plugin.Assembly = plugin.PluginLoader.LoadDefaultAssembly();
|
plugin.Assembly = plugin.PluginLoader.LoadDefaultAssembly();
|
||||||
@ -406,7 +418,7 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
OnPluginLoaded(new PluginEventArgs(plugin));
|
OnPluginLoaded(new PluginEventArgs(plugin));
|
||||||
return plugin;
|
return plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EnablePlugin(Plugin plugin, bool saveState, bool ignorePluginLock)
|
public void EnablePlugin(Plugin plugin, bool saveState, bool ignorePluginLock)
|
||||||
{
|
{
|
||||||
if (!plugin.Info.IsCompatible)
|
if (!plugin.Info.IsCompatible)
|
||||||
@ -431,10 +443,17 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
if (!plugin.Info.ArePrerequisitesMet())
|
if (!plugin.Info.ArePrerequisitesMet())
|
||||||
throw new ArtemisPluginPrerequisiteException(plugin.Info, "Cannot enable a plugin whose prerequisites aren't all met");
|
throw new ArtemisPluginPrerequisiteException(plugin.Info, "Cannot enable a plugin whose prerequisites aren't all met");
|
||||||
|
|
||||||
// Create the Ninject child kernel and load the module
|
// Create a child container for the plugin, be a bit more forgiving about concrete types
|
||||||
plugin.Kernel = new ChildKernel(_kernel, new PluginModule(plugin));
|
plugin.Container = _container.CreateChild(newRules: _container.Rules.WithConcreteTypeDynamicRegistrations());
|
||||||
// The kernel used by Core is unforgiving about missing bindings, no need to be so hard on plugin devs
|
try
|
||||||
plugin.Kernel.Components.Add<IMissingBindingResolver, SelfBindingResolver>();
|
{
|
||||||
|
plugin.Container.RegisterPlugin(plugin);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.Error(e, "Failed to register plugin services for plugin {plugin}, skipping enabling", plugin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
OnPluginEnabling(new PluginEventArgs(plugin));
|
OnPluginEnabling(new PluginEventArgs(plugin));
|
||||||
|
|
||||||
@ -446,11 +465,8 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
plugin.Kernel.Bind(featureInfo.FeatureType).ToSelf().InSingletonScope();
|
plugin.Container.Register(featureInfo.FeatureType, reuse: Reuse.Singleton);
|
||||||
|
PluginFeature instance = (PluginFeature) plugin.Container.Resolve(featureInfo.FeatureType);
|
||||||
// Include Plugin as a parameter for the PluginSettingsProvider
|
|
||||||
IParameter[] parameters = {new Parameter("Plugin", plugin, false)};
|
|
||||||
PluginFeature instance = (PluginFeature) plugin.Kernel.Get(featureInfo.FeatureType, parameters);
|
|
||||||
|
|
||||||
// Get the PluginFeature attribute which contains extra info on the feature
|
// Get the PluginFeature attribute which contains extra info on the feature
|
||||||
featureInfo.Instance = instance;
|
featureInfo.Instance = instance;
|
||||||
@ -526,8 +542,8 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
|
|
||||||
plugin.SetEnabled(false);
|
plugin.SetEnabled(false);
|
||||||
|
|
||||||
plugin.Kernel?.Dispose();
|
plugin.Container?.Dispose();
|
||||||
plugin.Kernel = null;
|
plugin.Container = null;
|
||||||
|
|
||||||
GC.Collect();
|
GC.Collect();
|
||||||
GC.WaitForPendingFinalizers();
|
GC.WaitForPendingFinalizers();
|
||||||
|
|||||||
@ -9,7 +9,7 @@ using Artemis.Core.Services.Models;
|
|||||||
using Artemis.Core.SkiaSharp;
|
using Artemis.Core.SkiaSharp;
|
||||||
using Artemis.Storage.Entities.Surface;
|
using Artemis.Storage.Entities.Surface;
|
||||||
using Artemis.Storage.Repositories.Interfaces;
|
using Artemis.Storage.Repositories.Interfaces;
|
||||||
using Ninject;
|
using DryIoc;
|
||||||
using RGB.NET.Core;
|
using RGB.NET.Core;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
@ -20,31 +20,36 @@ namespace Artemis.Core.Services;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal class RgbService : IRgbService
|
internal class RgbService : IRgbService
|
||||||
{
|
{
|
||||||
private readonly IDeviceRepository _deviceRepository;
|
|
||||||
private readonly List<ArtemisDevice> _devices;
|
|
||||||
private readonly List<ArtemisDevice> _enabledDevices;
|
|
||||||
private readonly IKernel _kernel;
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IPluginManagementService _pluginManagementService;
|
private readonly IPluginManagementService _pluginManagementService;
|
||||||
|
private readonly IDeviceRepository _deviceRepository;
|
||||||
|
private readonly LazyEnumerable<IGraphicsContextProvider> _graphicsContextProviders;
|
||||||
|
|
||||||
private readonly PluginSetting<string> _preferredGraphicsContext;
|
private readonly PluginSetting<string> _preferredGraphicsContext;
|
||||||
private readonly PluginSetting<double> _renderScaleSetting;
|
private readonly PluginSetting<double> _renderScaleSetting;
|
||||||
private readonly ISettingsService _settingsService;
|
|
||||||
private readonly PluginSetting<int> _targetFrameRateSetting;
|
private readonly PluginSetting<int> _targetFrameRateSetting;
|
||||||
|
|
||||||
|
private readonly List<ArtemisDevice> _devices;
|
||||||
|
private readonly List<ArtemisDevice> _enabledDevices;
|
||||||
private readonly SKTextureBrush _textureBrush = new(null) {CalculationMode = RenderMode.Absolute};
|
private readonly SKTextureBrush _textureBrush = new(null) {CalculationMode = RenderMode.Absolute};
|
||||||
private Dictionary<Led, ArtemisLed> _ledMap;
|
private Dictionary<Led, ArtemisLed> _ledMap;
|
||||||
private ListLedGroup? _surfaceLedGroup;
|
private ListLedGroup? _surfaceLedGroup;
|
||||||
private SKTexture? _texture;
|
private SKTexture? _texture;
|
||||||
|
|
||||||
public RgbService(ILogger logger, IKernel kernel, ISettingsService settingsService, IPluginManagementService pluginManagementService, IDeviceRepository deviceRepository)
|
public RgbService(ILogger logger,
|
||||||
|
ISettingsService settingsService,
|
||||||
|
IPluginManagementService pluginManagementService,
|
||||||
|
IDeviceRepository deviceRepository,
|
||||||
|
LazyEnumerable<IGraphicsContextProvider> graphicsContextProviders)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_kernel = kernel;
|
|
||||||
_settingsService = settingsService;
|
|
||||||
_pluginManagementService = pluginManagementService;
|
_pluginManagementService = pluginManagementService;
|
||||||
_deviceRepository = deviceRepository;
|
_deviceRepository = deviceRepository;
|
||||||
|
_graphicsContextProviders = graphicsContextProviders;
|
||||||
|
|
||||||
_targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 30);
|
_targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 30);
|
||||||
_renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.25);
|
_renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.25);
|
||||||
_preferredGraphicsContext = _settingsService.GetSetting("Core.PreferredGraphicsContext", "Software");
|
_preferredGraphicsContext = settingsService.GetSetting("Core.PreferredGraphicsContext", "Software");
|
||||||
|
|
||||||
Surface = new RGBSurface();
|
Surface = new RGBSurface();
|
||||||
Utilities.RenderScaleMultiplier = (int) (1 / _renderScaleSetting.Value);
|
Utilities.RenderScaleMultiplier = (int) (1 / _renderScaleSetting.Value);
|
||||||
@ -226,7 +231,7 @@ internal class RgbService : IRgbService
|
|||||||
{
|
{
|
||||||
_logger.Verbose("[AddDeviceProvider] Updating the LED group");
|
_logger.Verbose("[AddDeviceProvider] Updating the LED group");
|
||||||
UpdateLedGroup();
|
UpdateLedGroup();
|
||||||
|
|
||||||
_logger.Verbose("[AddDeviceProvider] Resuming rendering after adding {DeviceProvider}", deviceProvider.GetType().Name);
|
_logger.Verbose("[AddDeviceProvider] Resuming rendering after adding {DeviceProvider}", deviceProvider.GetType().Name);
|
||||||
if (changedRenderPaused)
|
if (changedRenderPaused)
|
||||||
SetRenderPaused(false);
|
SetRenderPaused(false);
|
||||||
@ -257,7 +262,7 @@ internal class RgbService : IRgbService
|
|||||||
{
|
{
|
||||||
_logger.Verbose("[RemoveDeviceProvider] Updating the LED group");
|
_logger.Verbose("[RemoveDeviceProvider] Updating the LED group");
|
||||||
UpdateLedGroup();
|
UpdateLedGroup();
|
||||||
|
|
||||||
_logger.Verbose("[RemoveDeviceProvider] Resuming rendering after adding {DeviceProvider}", deviceProvider.GetType().Name);
|
_logger.Verbose("[RemoveDeviceProvider] Resuming rendering after adding {DeviceProvider}", deviceProvider.GetType().Name);
|
||||||
if (changedRenderPaused)
|
if (changedRenderPaused)
|
||||||
SetRenderPaused(false);
|
SetRenderPaused(false);
|
||||||
@ -372,7 +377,8 @@ internal class RgbService : IRgbService
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<IGraphicsContextProvider> providers = _kernel.Get<List<IGraphicsContextProvider>>();
|
|
||||||
|
List<IGraphicsContextProvider> providers = _graphicsContextProviders.ToList();
|
||||||
if (!providers.Any())
|
if (!providers.Any())
|
||||||
{
|
{
|
||||||
_logger.Warning("No graphics context provider found, defaulting to software rendering");
|
_logger.Warning("No graphics context provider found, defaulting to software rendering");
|
||||||
|
|||||||
@ -2,10 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
|
||||||
using Artemis.Core.ScriptingProviders;
|
using Artemis.Core.ScriptingProviders;
|
||||||
using Ninject;
|
|
||||||
using Ninject.Parameters;
|
|
||||||
|
|
||||||
namespace Artemis.Core.Services;
|
namespace Artemis.Core.Services;
|
||||||
|
|
||||||
@ -51,11 +48,7 @@ internal class ScriptingService : IScriptingService
|
|||||||
if (provider == null)
|
if (provider == null)
|
||||||
throw new ArtemisCoreException($"Can't create script instance as there is no matching scripting provider found for the script ({scriptConfiguration.ScriptingProviderId}).");
|
throw new ArtemisCoreException($"Can't create script instance as there is no matching scripting provider found for the script ({scriptConfiguration.ScriptingProviderId}).");
|
||||||
|
|
||||||
script = (GlobalScript) provider.Plugin.Kernel!.Get(
|
script = (GlobalScript) provider.Plugin.Resolve(provider.GlobalScriptType, scriptConfiguration);
|
||||||
provider.GlobalScriptType,
|
|
||||||
CreateScriptConstructorArgument(provider.GlobalScriptType, scriptConfiguration)
|
|
||||||
);
|
|
||||||
|
|
||||||
script.ScriptingProvider = provider;
|
script.ScriptingProvider = provider;
|
||||||
script.ScriptingService = this;
|
script.ScriptingService = this;
|
||||||
scriptConfiguration.Script = script;
|
scriptConfiguration.Script = script;
|
||||||
@ -82,12 +75,7 @@ internal class ScriptingService : IScriptingService
|
|||||||
if (provider == null)
|
if (provider == null)
|
||||||
throw new ArtemisCoreException($"Can't create script instance as there is no matching scripting provider found for the script ({scriptConfiguration.ScriptingProviderId}).");
|
throw new ArtemisCoreException($"Can't create script instance as there is no matching scripting provider found for the script ({scriptConfiguration.ScriptingProviderId}).");
|
||||||
|
|
||||||
script = (ProfileScript) provider.Plugin.Kernel!.Get(
|
script = (ProfileScript) provider.Plugin.Resolve(provider.ProfileScriptType, profile, scriptConfiguration);
|
||||||
provider.ProfileScriptType,
|
|
||||||
CreateScriptConstructorArgument(provider.ProfileScriptType, profile),
|
|
||||||
CreateScriptConstructorArgument(provider.ProfileScriptType, scriptConfiguration)
|
|
||||||
);
|
|
||||||
|
|
||||||
script.ScriptingProvider = provider;
|
script.ScriptingProvider = provider;
|
||||||
scriptConfiguration.Script = script;
|
scriptConfiguration.Script = script;
|
||||||
provider.InternalScripts.Add(script);
|
provider.InternalScripts.Add(script);
|
||||||
@ -113,21 +101,6 @@ internal class ScriptingService : IScriptingService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConstructorArgument CreateScriptConstructorArgument(Type scriptType, object value)
|
|
||||||
{
|
|
||||||
// Limit to one constructor, there's no need to have more and it complicates things anyway
|
|
||||||
ConstructorInfo[] constructors = scriptType.GetConstructors();
|
|
||||||
if (constructors.Length != 1)
|
|
||||||
throw new ArtemisCoreException("Scripts must have exactly one constructor");
|
|
||||||
|
|
||||||
// Find the ScriptConfiguration parameter, it is required by the base constructor so its there for sure
|
|
||||||
ParameterInfo? configurationParameter = constructors.First().GetParameters().FirstOrDefault(p => value.GetType().IsAssignableFrom(p.ParameterType));
|
|
||||||
|
|
||||||
if (configurationParameter?.Name == null)
|
|
||||||
throw new ArtemisCoreException($"Couldn't find a valid constructor argument on {scriptType.Name} with type {value.GetType().Name}");
|
|
||||||
return new ConstructorArgument(configurationParameter.Name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeProfileScripts(Profile profile)
|
private void InitializeProfileScripts(Profile profile)
|
||||||
{
|
{
|
||||||
// Initialize the scripts on the profile
|
// Initialize the scripts on the profile
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using EmbedIO.WebApi;
|
using EmbedIO.WebApi;
|
||||||
using Ninject;
|
|
||||||
|
|
||||||
namespace Artemis.Core.Services;
|
namespace Artemis.Core.Services;
|
||||||
|
|
||||||
@ -8,7 +7,7 @@ internal class WebApiControllerRegistration<T> : WebApiControllerRegistration wh
|
|||||||
{
|
{
|
||||||
public WebApiControllerRegistration(PluginFeature feature) : base(feature, typeof(T))
|
public WebApiControllerRegistration(PluginFeature feature) : base(feature, typeof(T))
|
||||||
{
|
{
|
||||||
Factory = () => feature.Plugin.Kernel!.Get<T>();
|
Factory = () => feature.Plugin.Resolve<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Func<T> Factory { get; set; }
|
public Func<T> Factory { get; set; }
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using EmbedIO;
|
using EmbedIO;
|
||||||
using Ninject;
|
using DryIoc;
|
||||||
|
|
||||||
namespace Artemis.Core.Services;
|
namespace Artemis.Core.Services;
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ internal class WebModuleRegistration
|
|||||||
if (Create != null)
|
if (Create != null)
|
||||||
return Create();
|
return Create();
|
||||||
if (WebModuleType != null)
|
if (WebModuleType != null)
|
||||||
return (IWebModule) Feature.Plugin.Kernel!.Get(WebModuleType);
|
return (IWebModule) Feature.Plugin.Resolve(WebModuleType);
|
||||||
throw new ArtemisCoreException("WebModuleRegistration doesn't have a create function nor a web module type :(");
|
throw new ArtemisCoreException("WebModuleRegistration doesn't have a create function nor a web module type :(");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,6 +50,15 @@ public static class Utilities
|
|||||||
OnRestartRequested(new RestartEventArgs(elevate, delay, extraArgs.ToList()));
|
OnRestartRequested(new RestartEventArgs(elevate, delay, extraArgs.ToList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies a pending update
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="silent">A boolean indicating whether to silently update or not.</param>
|
||||||
|
public static void ApplyUpdate(bool silent)
|
||||||
|
{
|
||||||
|
OnUpdateRequested(new UpdateEventArgs(silent));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Opens the provided URL in the default web browser
|
/// Opens the provided URL in the default web browser
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -96,11 +105,16 @@ public static class Utilities
|
|||||||
/// Occurs when the core has requested an application shutdown
|
/// Occurs when the core has requested an application shutdown
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static event EventHandler? ShutdownRequested;
|
public static event EventHandler? ShutdownRequested;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when the core has requested an application restart
|
/// Occurs when the core has requested an application restart
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static event EventHandler<RestartEventArgs>? RestartRequested;
|
public static event EventHandler<RestartEventArgs>? RestartRequested;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when the core has requested a pending application update to be applied
|
||||||
|
/// </summary>
|
||||||
|
public static event EventHandler<UpdateEventArgs>? UpdateRequested;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Opens the provided folder in the user's file explorer
|
/// Opens the provided folder in the user's file explorer
|
||||||
@ -136,6 +150,11 @@ public static class Utilities
|
|||||||
{
|
{
|
||||||
ShutdownRequested?.Invoke(null, EventArgs.Empty);
|
ShutdownRequested?.Invoke(null, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void OnUpdateRequested(UpdateEventArgs e)
|
||||||
|
{
|
||||||
|
UpdateRequested?.Invoke(null, e);
|
||||||
|
}
|
||||||
|
|
||||||
#region Scaling
|
#region Scaling
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using Artemis.Storage.Entities.Profile.Nodes;
|
using Artemis.Storage.Entities.Profile.Nodes;
|
||||||
using Castle.Core.Internal;
|
|
||||||
|
|
||||||
namespace Artemis.Core;
|
namespace Artemis.Core;
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Artemis.Core.Events;
|
using Artemis.Core.Events;
|
||||||
|
using DryIoc;
|
||||||
|
|
||||||
namespace Artemis.Core;
|
namespace Artemis.Core;
|
||||||
|
|
||||||
@ -103,6 +104,8 @@ public abstract class Node : BreakableModel, INode
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string BrokenDisplayName => Name;
|
public override string BrokenDisplayName => Name;
|
||||||
|
|
||||||
|
internal IContainer Container { get; set; } = null!;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Construtors
|
#region Construtors
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Ninject;
|
using DryIoc;
|
||||||
using Ninject.Parameters;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a kind of node inside a <see cref="NodeScript" /> containing storage value of type
|
/// Represents a kind of node inside a <see cref="NodeScript" /> containing storage value of type
|
||||||
@ -22,26 +19,13 @@ public abstract class Node<TStorage, TViewModel> : Node<TStorage>, ICustomViewMo
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[Inject]
|
|
||||||
internal IKernel Kernel { get; set; } = null!;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when a view model is required
|
/// Called when a view model is required
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="nodeScript"></param>
|
/// <param name="nodeScript"></param>
|
||||||
public virtual TViewModel GetViewModel(NodeScript nodeScript)
|
public virtual TViewModel GetViewModel(NodeScript nodeScript)
|
||||||
{
|
{
|
||||||
// Limit to one constructor, there's no need to have more and it complicates things anyway
|
return Container.Resolve<TViewModel>(args: new object[] {this, nodeScript});
|
||||||
ConstructorInfo[] constructors = typeof(TViewModel).GetConstructors();
|
|
||||||
if (constructors.Length != 1)
|
|
||||||
throw new ArtemisCoreException("Node VMs must have exactly one constructor");
|
|
||||||
|
|
||||||
// Find the ScriptConfiguration parameter, it is required by the base constructor so its there for sure
|
|
||||||
ParameterInfo? configurationParameter = constructors.First().GetParameters().FirstOrDefault(p => GetType().IsAssignableFrom(p.ParameterType));
|
|
||||||
|
|
||||||
if (configurationParameter?.Name == null)
|
|
||||||
throw new ArtemisCoreException($"Couldn't find a valid constructor argument on {typeof(TViewModel).Name} with type {GetType().Name}");
|
|
||||||
return Kernel.Get<TViewModel>(new ConstructorArgument(configurationParameter.Name, this), new ConstructorArgument("script", nodeScript));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -9,7 +9,6 @@ public abstract class RenderElementEntity
|
|||||||
public Guid ParentId { get; set; }
|
public Guid ParentId { get; set; }
|
||||||
|
|
||||||
public List<LayerEffectEntity> LayerEffects { get; set; }
|
public List<LayerEffectEntity> LayerEffects { get; set; }
|
||||||
public List<PropertyEntity> PropertyEntities { get; set; }
|
|
||||||
|
|
||||||
public IConditionEntity DisplayCondition { get; set; }
|
public IConditionEntity DisplayCondition { get; set; }
|
||||||
public TimelineEntity Timeline { get; set; }
|
public TimelineEntity Timeline { get; set; }
|
||||||
|
|||||||
@ -9,7 +9,6 @@ public class FolderEntity : RenderElementEntity
|
|||||||
{
|
{
|
||||||
public FolderEntity()
|
public FolderEntity()
|
||||||
{
|
{
|
||||||
PropertyEntities = new List<PropertyEntity>();
|
|
||||||
LayerEffects = new List<LayerEffectEntity>();
|
LayerEffects = new List<LayerEffectEntity>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,6 @@ public class LayerEntity : RenderElementEntity
|
|||||||
{
|
{
|
||||||
Leds = new List<LedEntity>();
|
Leds = new List<LedEntity>();
|
||||||
AdaptionHints = new List<IAdaptionHintEntity>();
|
AdaptionHints = new List<IAdaptionHintEntity>();
|
||||||
PropertyEntities = new List<PropertyEntity>();
|
|
||||||
LayerEffects = new List<LayerEffectEntity>();
|
LayerEffects = new List<LayerEffectEntity>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
87
src/Artemis.Storage/Migrations/M0022TransitionNodes.cs
Normal file
87
src/Artemis.Storage/Migrations/M0022TransitionNodes.cs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Artemis.Storage.Entities.Profile;
|
||||||
|
using Artemis.Storage.Entities.Profile.Abstract;
|
||||||
|
using Artemis.Storage.Entities.Profile.Conditions;
|
||||||
|
using Artemis.Storage.Entities.Profile.Nodes;
|
||||||
|
using Artemis.Storage.Migrations.Interfaces;
|
||||||
|
using LiteDB;
|
||||||
|
|
||||||
|
namespace Artemis.Storage.Migrations;
|
||||||
|
|
||||||
|
public class M0022TransitionNodes : IStorageMigration
|
||||||
|
{
|
||||||
|
private void MigrateNodeScript(NodeScriptEntity nodeScript)
|
||||||
|
{
|
||||||
|
if (nodeScript == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (NodeEntity node in nodeScript.Nodes)
|
||||||
|
{
|
||||||
|
if (node.Type == "NumericEasingNode")
|
||||||
|
node.Type = "NumericTransitionNode";
|
||||||
|
else if (node.Type == "ColorGradientEasingNode")
|
||||||
|
node.Type = "ColorGradientTransitionNode";
|
||||||
|
else if (node.Type == "SKColorEasingNode")
|
||||||
|
node.Type = "SKColorTransitionNode";
|
||||||
|
else if (node.Type == "EasingTypeNode")
|
||||||
|
node.Type = "EasingFunctionNode";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MigratePropertyGroup(PropertyGroupEntity propertyGroup)
|
||||||
|
{
|
||||||
|
if (propertyGroup == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (PropertyGroupEntity childPropertyGroup in propertyGroup.PropertyGroups)
|
||||||
|
MigratePropertyGroup(childPropertyGroup);
|
||||||
|
foreach (PropertyEntity property in propertyGroup.Properties)
|
||||||
|
MigrateNodeScript(property.DataBinding?.NodeScript);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MigrateDisplayCondition(IConditionEntity conditionEntity)
|
||||||
|
{
|
||||||
|
if (conditionEntity is EventConditionEntity eventConditionEntity)
|
||||||
|
MigrateNodeScript(eventConditionEntity.Script);
|
||||||
|
else if (conditionEntity is StaticConditionEntity staticConditionEntity)
|
||||||
|
MigrateNodeScript(staticConditionEntity.Script);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int UserVersion => 22;
|
||||||
|
|
||||||
|
public void Apply(LiteRepository repository)
|
||||||
|
{
|
||||||
|
// Migrate profile configuration display conditions
|
||||||
|
List<ProfileCategoryEntity> categories = repository.Query<ProfileCategoryEntity>().ToList();
|
||||||
|
foreach (ProfileCategoryEntity profileCategoryEntity in categories)
|
||||||
|
{
|
||||||
|
foreach (ProfileConfigurationEntity profileConfigurationEntity in profileCategoryEntity.ProfileConfigurations)
|
||||||
|
MigrateNodeScript(profileConfigurationEntity.ActivationCondition);
|
||||||
|
repository.Update(profileCategoryEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate profile display conditions and data bindings
|
||||||
|
List<ProfileEntity> profiles = repository.Query<ProfileEntity>().ToList();
|
||||||
|
foreach (ProfileEntity profileEntity in profiles)
|
||||||
|
{
|
||||||
|
foreach (LayerEntity layer in profileEntity.Layers)
|
||||||
|
{
|
||||||
|
MigratePropertyGroup(layer.LayerBrush?.PropertyGroup);
|
||||||
|
MigratePropertyGroup(layer.GeneralPropertyGroup);
|
||||||
|
MigratePropertyGroup(layer.TransformPropertyGroup);
|
||||||
|
foreach (LayerEffectEntity layerEffectEntity in layer.LayerEffects)
|
||||||
|
MigratePropertyGroup(layerEffectEntity?.PropertyGroup);
|
||||||
|
MigrateDisplayCondition(layer.DisplayCondition);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (FolderEntity folder in profileEntity.Folders)
|
||||||
|
{
|
||||||
|
foreach (LayerEffectEntity folderLayerEffect in folder.LayerEffects)
|
||||||
|
MigratePropertyGroup(folderLayerEffect?.PropertyGroup);
|
||||||
|
MigrateDisplayCondition(folder.DisplayCondition);
|
||||||
|
}
|
||||||
|
|
||||||
|
repository.Update(profileEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -30,7 +30,7 @@ public static class StorageManager
|
|||||||
{
|
{
|
||||||
FileSystemInfo newest = files.OrderByDescending(fi => fi.CreationTime).First();
|
FileSystemInfo newest = files.OrderByDescending(fi => fi.CreationTime).First();
|
||||||
FileSystemInfo oldest = files.OrderBy(fi => fi.CreationTime).First();
|
FileSystemInfo oldest = files.OrderBy(fi => fi.CreationTime).First();
|
||||||
if (DateTime.Now - newest.CreationTime < TimeSpan.FromMinutes(10))
|
if (DateTime.Now - newest.CreationTime < TimeSpan.FromHours(12))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
oldest.Delete();
|
oldest.Delete();
|
||||||
|
|||||||
@ -10,10 +10,10 @@ namespace Artemis.Storage;
|
|||||||
public class StorageMigrationService
|
public class StorageMigrationService
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly List<IStorageMigration> _migrations;
|
private readonly IList<IStorageMigration> _migrations;
|
||||||
private readonly LiteRepository _repository;
|
private readonly LiteRepository _repository;
|
||||||
|
|
||||||
public StorageMigrationService(ILogger logger, LiteRepository repository, List<IStorageMigration> migrations)
|
public StorageMigrationService(ILogger logger, LiteRepository repository, IList<IStorageMigration> migrations)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_repository = repository;
|
_repository = repository;
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.UI.Linux.DryIoc;
|
||||||
using Artemis.UI.Linux.Providers.Input;
|
using Artemis.UI.Linux.Providers.Input;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Ninject;
|
using DryIoc;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Linux;
|
namespace Artemis.UI.Linux;
|
||||||
@ -13,12 +14,12 @@ namespace Artemis.UI.Linux;
|
|||||||
public class App : Application
|
public class App : Application
|
||||||
{
|
{
|
||||||
private ApplicationStateManager? _applicationStateManager;
|
private ApplicationStateManager? _applicationStateManager;
|
||||||
private StandardKernel? _kernel;
|
private IContainer? _container;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
_kernel = ArtemisBootstrapper.Bootstrap(this);
|
_container = ArtemisBootstrapper.Bootstrap(this, c => c.RegisterProviders());
|
||||||
Program.CreateLogger(_kernel);
|
Program.CreateLogger(_container);
|
||||||
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
|
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
|
||||||
@ -32,15 +33,15 @@ public class App : Application
|
|||||||
|
|
||||||
ArtemisBootstrapper.Initialize();
|
ArtemisBootstrapper.Initialize();
|
||||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
_applicationStateManager = new ApplicationStateManager(_kernel!, desktop.Args);
|
_applicationStateManager = new ApplicationStateManager(_container!, desktop.Args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RegisterProviders()
|
private void RegisterProviders()
|
||||||
{
|
{
|
||||||
IInputService inputService = _kernel.Get<IInputService>();
|
IInputService inputService = _container.Resolve<IInputService>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
inputService.AddInputProvider(_kernel.Get<LinuxInputProvider>());
|
inputService.AddInputProvider(_container.Resolve<InputProvider>(LinuxInputProvider.Id));
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@ -11,7 +11,7 @@ using Artemis.UI.Shared.Services;
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Ninject;
|
using DryIoc;
|
||||||
|
|
||||||
namespace Artemis.UI.Linux;
|
namespace Artemis.UI.Linux;
|
||||||
|
|
||||||
@ -22,23 +22,23 @@ public class ApplicationStateManager
|
|||||||
// ReSharper disable once NotAccessedField.Local - Kept in scope to ensure it does not get released
|
// ReSharper disable once NotAccessedField.Local - Kept in scope to ensure it does not get released
|
||||||
private Mutex? _artemisMutex;
|
private Mutex? _artemisMutex;
|
||||||
|
|
||||||
public ApplicationStateManager(IKernel kernel, string[] startupArguments)
|
public ApplicationStateManager(IContainer container, string[] startupArguments)
|
||||||
{
|
{
|
||||||
_windowService = kernel.Get<IWindowService>();
|
_windowService = container.Resolve<IWindowService>();
|
||||||
StartupArguments = startupArguments;
|
StartupArguments = startupArguments;
|
||||||
|
|
||||||
Core.Utilities.ShutdownRequested += UtilitiesOnShutdownRequested;
|
Core.Utilities.ShutdownRequested += UtilitiesOnShutdownRequested;
|
||||||
Core.Utilities.RestartRequested += UtilitiesOnRestartRequested;
|
Core.Utilities.RestartRequested += UtilitiesOnRestartRequested;
|
||||||
|
|
||||||
// On OS shutdown dispose the kernel just so device providers get a chance to clean up
|
// On OS shutdown dispose the IOC container just so device providers get a chance to clean up
|
||||||
if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime)
|
if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime)
|
||||||
controlledApplicationLifetime.Exit += (_, _) =>
|
controlledApplicationLifetime.Exit += (_, _) =>
|
||||||
{
|
{
|
||||||
RunForcedShutdownIfEnabled();
|
RunForcedShutdownIfEnabled();
|
||||||
|
|
||||||
// Dispose plugins before disposing the kernel because plugins might access services during dispose
|
// Dispose plugins before disposing the IOC container because plugins might access services during dispose
|
||||||
kernel.Get<IPluginManagementService>().Dispose();
|
container.Resolve<IPluginManagementService>().Dispose();
|
||||||
kernel.Dispose();
|
container.Dispose();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
20
src/Artemis.UI.Linux/DryIoc/ContainerExtensions.cs
Normal file
20
src/Artemis.UI.Linux/DryIoc/ContainerExtensions.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.UI.Linux.Providers.Input;
|
||||||
|
using DryIoc;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Linux.DryIoc;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides an extension method to register services onto a DryIoc <see cref="IContainer"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class UIContainerExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Registers providers into the container.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="container">The builder building the current container</param>
|
||||||
|
public static void RegisterProviders(this IContainer container)
|
||||||
|
{
|
||||||
|
container.Register<InputProvider, LinuxInputProvider>(serviceKey: LinuxInputProvider.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.Storage;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using Ninject;
|
using DryIoc;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Artemis.UI.Linux;
|
namespace Artemis.UI.Linux;
|
||||||
@ -18,6 +20,7 @@ internal class Program
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
StorageManager.CreateBackup(Constants.DataFolder);
|
||||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@ -33,8 +36,8 @@ internal class Program
|
|||||||
return AppBuilder.Configure<App>().UsePlatformDetect().LogToTrace().UseReactiveUI();
|
return AppBuilder.Configure<App>().UsePlatformDetect().LogToTrace().UseReactiveUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CreateLogger(IKernel kernel)
|
public static void CreateLogger(IContainer container)
|
||||||
{
|
{
|
||||||
Logger = kernel.Get<ILogger>().ForContext<Program>();
|
Logger = container.Resolve<ILogger>().ForContext<Program>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -26,6 +26,8 @@ public class LinuxInputProvider : InputProvider
|
|||||||
_readers.Add(reader);
|
_readers.Add(reader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Guid Id { get; } = new("72a6fe5c-b11e-4886-bd48-b3ff5d9006c1");
|
||||||
|
|
||||||
#region Overrides of InputProvider
|
#region Overrides of InputProvider
|
||||||
|
|
||||||
|
|||||||
@ -2,19 +2,19 @@ using Avalonia;
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Ninject;
|
using DryIoc;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.MacOS;
|
namespace Artemis.UI.MacOS;
|
||||||
|
|
||||||
public class App : Application
|
public class App : Application
|
||||||
{
|
{
|
||||||
private StandardKernel? _kernel;
|
private IContainer? _container;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
_kernel = ArtemisBootstrapper.Bootstrap(this);
|
_container = ArtemisBootstrapper.Bootstrap(this);
|
||||||
Program.CreateLogger(_kernel);
|
Program.CreateLogger(_container);
|
||||||
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
|
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.Storage;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using Ninject;
|
using DryIoc;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Artemis.UI.MacOS;
|
namespace Artemis.UI.MacOS;
|
||||||
@ -18,6 +20,7 @@ internal class Program
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
StorageManager.CreateBackup(Constants.DataFolder);
|
||||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@ -33,8 +36,8 @@ internal class Program
|
|||||||
return AppBuilder.Configure<App>().UsePlatformDetect().LogToTrace().UseReactiveUI();
|
return AppBuilder.Configure<App>().UsePlatformDetect().LogToTrace().UseReactiveUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CreateLogger(IKernel kernel)
|
public static void CreateLogger(IContainer container)
|
||||||
{
|
{
|
||||||
Logger = kernel.Get<ILogger>().ForContext<Program>();
|
Logger = container.Resolve<ILogger>().ForContext<Program>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -8,6 +8,7 @@ using Avalonia.Data;
|
|||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
using FluentAvalonia.Core;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using Material.Icons;
|
using Material.Icons;
|
||||||
|
|
||||||
@ -47,7 +48,8 @@ public class HotkeyBox : UserControl
|
|||||||
Hotkey.Key = (KeyboardKey?) e.Key;
|
Hotkey.Key = (KeyboardKey?) e.Key;
|
||||||
Hotkey.Modifiers = (KeyboardModifierKey?) e.KeyModifiers;
|
Hotkey.Modifiers = (KeyboardModifierKey?) e.KeyModifiers;
|
||||||
UpdateDisplayTextBox();
|
UpdateDisplayTextBox();
|
||||||
|
HotkeyChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,4 +136,13 @@ public class HotkeyBox : UserControl
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Events
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when the hotkey changes.
|
||||||
|
/// </summary>
|
||||||
|
public event TypedEventHandler<HotkeyBox, EventArgs>? HotkeyChanged;
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
21
src/Artemis.UI.Shared/DryIoc/ContainerExtensions.cs
Normal file
21
src/Artemis.UI.Shared/DryIoc/ContainerExtensions.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using Artemis.UI.Shared.Services;
|
||||||
|
using DryIoc;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared.DryIoc;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides an extension method to register services onto a DryIoc <see cref="IContainer"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class UIContainerExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Registers shared UI services into the container.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="container">The builder building the current container</param>
|
||||||
|
public static void RegisterSharedUI(this IContainer container)
|
||||||
|
{
|
||||||
|
Assembly artemisShared = typeof(IArtemisSharedUIService).GetAssembly();
|
||||||
|
container.RegisterMany(new[] { artemisShared }, type => type.IsAssignableTo<IArtemisSharedUIService>(), Reuse.Singleton);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,30 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Artemis.UI.Shared.Services;
|
|
||||||
using Ninject.Extensions.Conventions;
|
|
||||||
using Ninject.Modules;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared.Ninject;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The main <see cref="NinjectModule" /> of the Artemis Shared UI toolkit that binds all services
|
|
||||||
/// </summary>
|
|
||||||
public class SharedUIModule : NinjectModule
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Load()
|
|
||||||
{
|
|
||||||
if (Kernel == null)
|
|
||||||
throw new ArgumentNullException("Kernel shouldn't be null here.");
|
|
||||||
|
|
||||||
// Bind all shared UI services as singletons
|
|
||||||
Kernel.Bind(x =>
|
|
||||||
{
|
|
||||||
x.FromAssemblyContaining<IArtemisSharedUIService>()
|
|
||||||
.IncludingNonPublicTypes()
|
|
||||||
.SelectAllClasses()
|
|
||||||
.InheritedFrom<IArtemisSharedUIService>()
|
|
||||||
.BindAllInterfaces()
|
|
||||||
.Configure(c => c.InSingletonScope());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared.Providers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a provider for custom cursors.
|
|
||||||
/// </summary>
|
|
||||||
public interface IUpdateProvider
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Asynchronously checks whether an update is available.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="channel">The channel to use when checking updates (i.e. master or development)</param>
|
|
||||||
/// <returns>A task returning <see langword="true" /> if an update is available; otherwise <see langword="false" />.</returns>
|
|
||||||
Task<bool> CheckForUpdate(string channel);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Applies any available updates.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="channel">The channel to use when checking updates (i.e. master or development)</param>
|
|
||||||
/// <param name="silent">Whether or not to update silently.</param>
|
|
||||||
Task ApplyUpdate(string channel, bool silent);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Offer to install the update to the user.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="channel">The channel to use when checking updates (i.e. master or development)</param>
|
|
||||||
/// <param name="windowOpen">A boolean indicating whether the main window is open.</param>
|
|
||||||
/// <returns>A task returning <see langword="true" /> if the user chose to update; otherwise <see langword="false" />.</returns>
|
|
||||||
Task OfferUpdate(string channel, bool windowOpen);
|
|
||||||
}
|
|
||||||
@ -3,9 +3,8 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using DryIoc;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using Ninject;
|
|
||||||
using Ninject.Parameters;
|
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared.Services.Builders;
|
namespace Artemis.UI.Shared.Services.Builders;
|
||||||
@ -16,13 +15,13 @@ namespace Artemis.UI.Shared.Services.Builders;
|
|||||||
public class ContentDialogBuilder
|
public class ContentDialogBuilder
|
||||||
{
|
{
|
||||||
private readonly ContentDialog _contentDialog;
|
private readonly ContentDialog _contentDialog;
|
||||||
private readonly IKernel _kernel;
|
private readonly IContainer _container;
|
||||||
private readonly Window _parent;
|
private readonly Window _parent;
|
||||||
private ContentDialogViewModelBase? _viewModel;
|
private ContentDialogViewModelBase? _viewModel;
|
||||||
|
|
||||||
internal ContentDialogBuilder(IKernel kernel, Window parent)
|
internal ContentDialogBuilder(IContainer container, Window parent)
|
||||||
{
|
{
|
||||||
_kernel = kernel;
|
_container = container;
|
||||||
_parent = parent;
|
_parent = parent;
|
||||||
_contentDialog = new ContentDialog
|
_contentDialog = new ContentDialog
|
||||||
{
|
{
|
||||||
@ -129,12 +128,11 @@ public class ContentDialogBuilder
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The type of the view model to host.</typeparam>
|
/// <typeparam name="T">The type of the view model to host.</typeparam>
|
||||||
/// <param name="viewModel">The resulting view model.</param>
|
/// <param name="viewModel">The resulting view model.</param>
|
||||||
/// <param name="parameters">Optional parameters to pass to the constructor of the view model, case and order sensitive.</param>
|
/// <param name="parameters">Optional parameters to pass to the constructor of the view model.</param>
|
||||||
/// <returns>The builder that can be used to further build the dialog.</returns>
|
/// <returns>The builder that can be used to further build the dialog.</returns>
|
||||||
public ContentDialogBuilder WithViewModel<T>(out T viewModel, params (string name, object? value)[] parameters) where T : ContentDialogViewModelBase
|
public ContentDialogBuilder WithViewModel<T>(out T viewModel, params object[] parameters) where T : ContentDialogViewModelBase
|
||||||
{
|
{
|
||||||
IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast<IParameter>().ToArray();
|
viewModel = _container.Resolve<T>(parameters);
|
||||||
viewModel = _kernel.Get<T>(paramsArray);
|
|
||||||
viewModel.ContentDialog = _contentDialog;
|
viewModel.ContentDialog = _contentDialog;
|
||||||
_contentDialog.Content = viewModel;
|
_contentDialog.Content = viewModel;
|
||||||
|
|
||||||
|
|||||||
@ -8,22 +8,21 @@ using Artemis.Core.Services;
|
|||||||
using Artemis.UI.Shared.DataModelVisualization;
|
using Artemis.UI.Shared.DataModelVisualization;
|
||||||
using Artemis.UI.Shared.DataModelVisualization.Shared;
|
using Artemis.UI.Shared.DataModelVisualization.Shared;
|
||||||
using Artemis.UI.Shared.DefaultTypes.DataModel.Display;
|
using Artemis.UI.Shared.DefaultTypes.DataModel.Display;
|
||||||
using Ninject;
|
using DryIoc;
|
||||||
using Ninject.Parameters;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared.Services;
|
namespace Artemis.UI.Shared.Services;
|
||||||
|
|
||||||
internal class DataModelUIService : IDataModelUIService
|
internal class DataModelUIService : IDataModelUIService
|
||||||
{
|
{
|
||||||
private readonly IDataModelService _dataModelService;
|
private readonly IDataModelService _dataModelService;
|
||||||
private readonly IKernel _kernel;
|
private readonly IContainer _container;
|
||||||
private readonly List<DataModelVisualizationRegistration> _registeredDataModelDisplays;
|
private readonly List<DataModelVisualizationRegistration> _registeredDataModelDisplays;
|
||||||
private readonly List<DataModelVisualizationRegistration> _registeredDataModelEditors;
|
private readonly List<DataModelVisualizationRegistration> _registeredDataModelEditors;
|
||||||
|
|
||||||
public DataModelUIService(IDataModelService dataModelService, IKernel kernel)
|
public DataModelUIService(IDataModelService dataModelService, IContainer container)
|
||||||
{
|
{
|
||||||
_dataModelService = dataModelService;
|
_dataModelService = dataModelService;
|
||||||
_kernel = kernel;
|
_container = container;
|
||||||
_registeredDataModelEditors = new List<DataModelVisualizationRegistration>();
|
_registeredDataModelEditors = new List<DataModelVisualizationRegistration>();
|
||||||
_registeredDataModelDisplays = new List<DataModelVisualizationRegistration>();
|
_registeredDataModelDisplays = new List<DataModelVisualizationRegistration>();
|
||||||
|
|
||||||
@ -36,18 +35,8 @@ internal class DataModelUIService : IDataModelUIService
|
|||||||
// This assumes the type can be converted, that has been checked when the VM was created
|
// This assumes the type can be converted, that has been checked when the VM was created
|
||||||
if (initialValue != null && initialValue.GetType() != registration.SupportedType)
|
if (initialValue != null && initialValue.GetType() != registration.SupportedType)
|
||||||
initialValue = Convert.ChangeType(initialValue, registration.SupportedType);
|
initialValue = Convert.ChangeType(initialValue, registration.SupportedType);
|
||||||
|
|
||||||
IParameter[] parameters =
|
DataModelInputViewModel viewModel = (DataModelInputViewModel) registration.Plugin.Resolve(registration.ViewModelType, description, initialValue);
|
||||||
{
|
|
||||||
new ConstructorArgument("targetDescription", description),
|
|
||||||
new ConstructorArgument("initialValue", initialValue)
|
|
||||||
};
|
|
||||||
|
|
||||||
// If this ever happens something is likely wrong with the plugin unload detection
|
|
||||||
if (registration.Plugin.Kernel == null)
|
|
||||||
throw new ArtemisSharedUIException("Cannot InstantiateDataModelInputViewModel for a registration by an uninitialized plugin");
|
|
||||||
|
|
||||||
DataModelInputViewModel viewModel = (DataModelInputViewModel) registration.Plugin.Kernel.Get(registration.ViewModelType, parameters);
|
|
||||||
viewModel.CompatibleConversionTypes = registration.CompatibleConversionTypes;
|
viewModel.CompatibleConversionTypes = registration.CompatibleConversionTypes;
|
||||||
return viewModel;
|
return viewModel;
|
||||||
}
|
}
|
||||||
@ -133,7 +122,7 @@ internal class DataModelUIService : IDataModelUIService
|
|||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
_kernel.Bind(viewModelType).ToSelf();
|
_container.Register(viewModelType, ifAlreadyRegistered: IfAlreadyRegistered.Replace);
|
||||||
|
|
||||||
// Create the registration
|
// Create the registration
|
||||||
DataModelVisualizationRegistration registration = new(this, RegistrationType.Input, plugin, supportedType, viewModelType)
|
DataModelVisualizationRegistration registration = new(this, RegistrationType.Input, plugin, supportedType, viewModelType)
|
||||||
@ -162,7 +151,7 @@ internal class DataModelUIService : IDataModelUIService
|
|||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
_kernel.Bind(viewModelType).ToSelf();
|
_container.Register(viewModelType);
|
||||||
DataModelVisualizationRegistration registration = new(this, RegistrationType.Display, plugin, supportedType, viewModelType);
|
DataModelVisualizationRegistration registration = new(this, RegistrationType.Display, plugin, supportedType, viewModelType);
|
||||||
_registeredDataModelDisplays.Add(registration);
|
_registeredDataModelDisplays.Add(registration);
|
||||||
return registration;
|
return registration;
|
||||||
@ -178,7 +167,8 @@ internal class DataModelUIService : IDataModelUIService
|
|||||||
registration.Unsubscribe();
|
registration.Unsubscribe();
|
||||||
_registeredDataModelEditors.Remove(registration);
|
_registeredDataModelEditors.Remove(registration);
|
||||||
|
|
||||||
_kernel.Unbind(registration.ViewModelType);
|
_container.Unregister(registration.ViewModelType);
|
||||||
|
_container.ClearCache(registration.ViewModelType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -192,7 +182,8 @@ internal class DataModelUIService : IDataModelUIService
|
|||||||
registration.Unsubscribe();
|
registration.Unsubscribe();
|
||||||
_registeredDataModelDisplays.Remove(registration);
|
_registeredDataModelDisplays.Remove(registration);
|
||||||
|
|
||||||
_kernel.Unbind(registration.ViewModelType);
|
_container.Unregister(registration.ViewModelType);
|
||||||
|
_container.ClearCache(registration.ViewModelType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -205,21 +196,11 @@ internal class DataModelUIService : IDataModelUIService
|
|||||||
|
|
||||||
DataModelVisualizationRegistration? match = _registeredDataModelDisplays.FirstOrDefault(d => d.SupportedType == propertyType);
|
DataModelVisualizationRegistration? match = _registeredDataModelDisplays.FirstOrDefault(d => d.SupportedType == propertyType);
|
||||||
if (match != null)
|
if (match != null)
|
||||||
{
|
result = (DataModelDisplayViewModel) match.Plugin.Resolve(match.ViewModelType);
|
||||||
// If this ever happens something is likely wrong with the plugin unload detection
|
|
||||||
if (match.Plugin.Kernel == null)
|
|
||||||
throw new ArtemisSharedUIException("Cannot GetDataModelDisplayViewModel for a registration by an uninitialized plugin");
|
|
||||||
|
|
||||||
result = (DataModelDisplayViewModel) match.Plugin.Kernel.Get(match.ViewModelType);
|
|
||||||
}
|
|
||||||
else if (!fallBackToDefault)
|
else if (!fallBackToDefault)
|
||||||
{
|
|
||||||
result = null;
|
result = null;
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
result = _container.Resolve<DefaultDataModelDisplayViewModel>();
|
||||||
result = _kernel.Get<DefaultDataModelDisplayViewModel>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result != null)
|
if (result != null)
|
||||||
result.PropertyDescription = description;
|
result.PropertyDescription = description;
|
||||||
|
|||||||
@ -16,7 +16,7 @@ public interface IWindowService : IArtemisSharedUIService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TViewModel">The type of view model to create</typeparam>
|
/// <typeparam name="TViewModel">The type of view model to create</typeparam>
|
||||||
/// <returns>The created view model</returns>
|
/// <returns>The created view model</returns>
|
||||||
TViewModel ShowWindow<TViewModel>(params (string name, object value)[] parameters);
|
TViewModel ShowWindow<TViewModel>(params object[] parameters);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Given a ViewModel, show its corresponding View as a window
|
/// Given a ViewModel, show its corresponding View as a window
|
||||||
@ -37,7 +37,7 @@ public interface IWindowService : IArtemisSharedUIService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TViewModel">The type of view model to create</typeparam>
|
/// <typeparam name="TViewModel">The type of view model to create</typeparam>
|
||||||
/// <returns>The created view model</returns>
|
/// <returns>The created view model</returns>
|
||||||
Task<TViewModel> ShowDialogAsync<TViewModel>(params (string name, object value)[] parameters);
|
Task<TViewModel> ShowDialogAsync<TViewModel>(params object[] parameters);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Given a ViewModel, show its corresponding View as a dialog
|
/// Given a ViewModel, show its corresponding View as a dialog
|
||||||
@ -60,7 +60,7 @@ public interface IWindowService : IArtemisSharedUIService
|
|||||||
/// <typeparam name="TViewModel">The view model type</typeparam>
|
/// <typeparam name="TViewModel">The view model type</typeparam>
|
||||||
/// <typeparam name="TResult">The return type</typeparam>
|
/// <typeparam name="TResult">The return type</typeparam>
|
||||||
/// <returns>A task containing the return value of type <typeparamref name="TResult" /></returns>
|
/// <returns>A task containing the return value of type <typeparamref name="TResult" /></returns>
|
||||||
Task<TResult> ShowDialogAsync<TViewModel, TResult>(params (string name, object? value)[] parameters) where TViewModel : DialogViewModelBase<TResult>;
|
Task<TResult> ShowDialogAsync<TViewModel, TResult>(params object[] parameters) where TViewModel : DialogViewModelBase<TResult>;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Shows a content dialog asking the user to confirm an action
|
/// Shows a content dialog asking the user to confirm an action
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using Artemis.Core;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
|
namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||||
|
|
||||||
|
|||||||
@ -3,19 +3,18 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Ninject;
|
using DryIoc;
|
||||||
using Ninject.Parameters;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared.Services.PropertyInput;
|
namespace Artemis.UI.Shared.Services.PropertyInput;
|
||||||
|
|
||||||
internal class PropertyInputService : IPropertyInputService
|
internal class PropertyInputService : IPropertyInputService
|
||||||
{
|
{
|
||||||
private readonly IKernel _kernel;
|
private readonly IContainer _container;
|
||||||
private readonly List<PropertyInputRegistration> _registeredPropertyEditors;
|
private readonly List<PropertyInputRegistration> _registeredPropertyEditors;
|
||||||
|
|
||||||
public PropertyInputService(IKernel kernel)
|
public PropertyInputService(IContainer container)
|
||||||
{
|
{
|
||||||
_kernel = kernel;
|
_container = container;
|
||||||
_registeredPropertyEditors = new List<PropertyInputRegistration>();
|
_registeredPropertyEditors = new List<PropertyInputRegistration>();
|
||||||
RegisteredPropertyEditors = new ReadOnlyCollection<PropertyInputRegistration>(_registeredPropertyEditors);
|
RegisteredPropertyEditors = new ReadOnlyCollection<PropertyInputRegistration>(_registeredPropertyEditors);
|
||||||
}
|
}
|
||||||
@ -55,7 +54,7 @@ internal class PropertyInputService : IPropertyInputService
|
|||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
_kernel.Bind(viewModelType).ToSelf();
|
_container.Register(viewModelType);
|
||||||
PropertyInputRegistration registration = new(this, plugin, supportedType, viewModelType);
|
PropertyInputRegistration registration = new(this, plugin, supportedType, viewModelType);
|
||||||
_registeredPropertyEditors.Add(registration);
|
_registeredPropertyEditors.Add(registration);
|
||||||
return registration;
|
return registration;
|
||||||
@ -71,7 +70,8 @@ internal class PropertyInputService : IPropertyInputService
|
|||||||
registration.Unsubscribe();
|
registration.Unsubscribe();
|
||||||
_registeredPropertyEditors.Remove(registration);
|
_registeredPropertyEditors.Remove(registration);
|
||||||
|
|
||||||
_kernel.Unbind(registration.ViewModelType);
|
_container.Unregister(registration.ViewModelType);
|
||||||
|
_container.ClearCache(registration.ViewModelType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,10 +110,9 @@ internal class PropertyInputService : IPropertyInputService
|
|||||||
if (viewModelType == null)
|
if (viewModelType == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
ConstructorArgument parameter = new("layerProperty", layerProperty);
|
|
||||||
// ReSharper disable once InconsistentlySynchronizedField
|
// ReSharper disable once InconsistentlySynchronizedField
|
||||||
// When you've just spent the last 2 hours trying to figure out a deadlock and reach this line, I'm so, so sorry. I thought this would be fine.
|
// When you've just spent the last 2 hours trying to figure out a deadlock and reach this line, I'm so, so sorry. I thought this would be fine.
|
||||||
IKernel kernel = registration?.Plugin.Kernel ?? _kernel;
|
IContainer container = registration?.Plugin.Container ?? _container;
|
||||||
return (PropertyInputViewModel<T>) kernel.Get(viewModelType, parameter);
|
return (PropertyInputViewModel<T>) container.Resolve(viewModelType, args: new object[] { layerProperty });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6,34 +6,33 @@ using Avalonia;
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
|
using DryIoc;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using Ninject;
|
|
||||||
using Ninject.Parameters;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared.Services;
|
namespace Artemis.UI.Shared.Services;
|
||||||
|
|
||||||
internal class WindowService : IWindowService
|
internal class WindowService : IWindowService
|
||||||
{
|
{
|
||||||
private readonly IKernel _kernel;
|
private readonly IContainer _container;
|
||||||
private bool _exceptionDialogOpen;
|
private bool _exceptionDialogOpen;
|
||||||
|
|
||||||
public WindowService(IKernel kernel)
|
public WindowService(IContainer container)
|
||||||
{
|
{
|
||||||
_kernel = kernel;
|
_container = container;
|
||||||
}
|
}
|
||||||
|
|
||||||
public T ShowWindow<T>(params (string name, object value)[] parameters)
|
public T ShowWindow<T>(params object[] parameters)
|
||||||
{
|
{
|
||||||
IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast<IParameter>().ToArray();
|
T viewModel = _container.Resolve<T>(parameters);
|
||||||
T viewModel = _kernel.Get<T>(paramsArray)!;
|
if (viewModel == null)
|
||||||
|
throw new ArtemisSharedUIException($"Failed to show window for VM of type {typeof(T).Name}, could not create instance.");
|
||||||
|
|
||||||
ShowWindow(viewModel);
|
ShowWindow(viewModel);
|
||||||
return viewModel;
|
return viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Window ShowWindow(object viewModel)
|
public Window ShowWindow(object viewModel)
|
||||||
{
|
{
|
||||||
Window? parent = GetCurrentWindow();
|
|
||||||
|
|
||||||
string name = viewModel.GetType().FullName!.Split('`')[0].Replace("ViewModel", "View");
|
string name = viewModel.GetType().FullName!.Split('`')[0].Replace("ViewModel", "View");
|
||||||
Type? type = viewModel.GetType().Assembly.GetType(name);
|
Type? type = viewModel.GetType().Assembly.GetType(name);
|
||||||
|
|
||||||
@ -45,18 +44,17 @@ internal class WindowService : IWindowService
|
|||||||
|
|
||||||
Window window = (Window) Activator.CreateInstance(type)!;
|
Window window = (Window) Activator.CreateInstance(type)!;
|
||||||
window.DataContext = viewModel;
|
window.DataContext = viewModel;
|
||||||
if (parent != null)
|
window.Show();
|
||||||
window.Show(parent);
|
|
||||||
else
|
|
||||||
window.Show();
|
|
||||||
|
|
||||||
return window;
|
return window;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<T> ShowDialogAsync<T>(params (string name, object value)[] parameters)
|
public async Task<T> ShowDialogAsync<T>(params object[] parameters)
|
||||||
{
|
{
|
||||||
IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast<IParameter>().ToArray();
|
T viewModel = _container.Resolve<T>(parameters);
|
||||||
T viewModel = _kernel.Get<T>(paramsArray)!;
|
if (viewModel == null)
|
||||||
|
throw new ArtemisSharedUIException($"Failed to show window for VM of type {typeof(T).Name}, could not create instance.");
|
||||||
|
|
||||||
await ShowDialogAsync(viewModel);
|
await ShowDialogAsync(viewModel);
|
||||||
return viewModel;
|
return viewModel;
|
||||||
}
|
}
|
||||||
@ -79,10 +77,12 @@ internal class WindowService : IWindowService
|
|||||||
await window.ShowDialog(parent);
|
await window.ShowDialog(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<TResult> ShowDialogAsync<TViewModel, TResult>(params (string name, object? value)[] parameters) where TViewModel : DialogViewModelBase<TResult>
|
public async Task<TResult> ShowDialogAsync<TViewModel, TResult>(params object[] parameters) where TViewModel : DialogViewModelBase<TResult>
|
||||||
{
|
{
|
||||||
IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast<IParameter>().ToArray();
|
TViewModel viewModel = _container.Resolve<TViewModel>(parameters);
|
||||||
TViewModel viewModel = _kernel.Get<TViewModel>(paramsArray)!;
|
if (viewModel == null)
|
||||||
|
throw new ArtemisSharedUIException($"Failed to show window for VM of type {typeof(TViewModel).Name}, could not create instance.");
|
||||||
|
|
||||||
return await ShowDialogAsync(viewModel);
|
return await ShowDialogAsync(viewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +129,7 @@ internal class WindowService : IWindowService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ShowDialogAsync(new ExceptionDialogViewModel(title, exception, _kernel.Get<INotificationService>()));
|
await ShowDialogAsync(new ExceptionDialogViewModel(title, exception, _container.Resolve<INotificationService>()));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -143,7 +143,7 @@ internal class WindowService : IWindowService
|
|||||||
Window? currentWindow = GetCurrentWindow();
|
Window? currentWindow = GetCurrentWindow();
|
||||||
if (currentWindow == null)
|
if (currentWindow == null)
|
||||||
throw new ArtemisSharedUIException("Can't show a content dialog without any windows being shown.");
|
throw new ArtemisSharedUIException("Can't show a content dialog without any windows being shown.");
|
||||||
return new ContentDialogBuilder(_kernel, currentWindow);
|
return new ContentDialogBuilder(_container, currentWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
public OpenFolderDialogBuilder CreateOpenFolderDialog()
|
public OpenFolderDialogBuilder CreateOpenFolderDialog()
|
||||||
|
|||||||
@ -39,6 +39,9 @@
|
|||||||
|
|
||||||
<dataModelPicker:DataModelPickerButton></dataModelPicker:DataModelPickerButton>
|
<dataModelPicker:DataModelPickerButton></dataModelPicker:DataModelPickerButton>
|
||||||
<dataModelPicker:DataModelPickerButton Classes="condensed"></dataModelPicker:DataModelPickerButton>
|
<dataModelPicker:DataModelPickerButton Classes="condensed"></dataModelPicker:DataModelPickerButton>
|
||||||
|
|
||||||
|
<controls1:HotkeyBox></controls1:HotkeyBox>
|
||||||
|
<controls1:HotkeyBox Classes="condensed"></controls1:HotkeyBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</Design.PreviewWith>
|
</Design.PreviewWith>
|
||||||
@ -110,4 +113,10 @@
|
|||||||
<Setter Property="MinHeight" Value="24" />
|
<Setter Property="MinHeight" Value="24" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="controls1|HotkeyBox.condensed > TextBox#DisplayTextBox">
|
||||||
|
<Setter Property="Padding" Value="6 0" />
|
||||||
|
<Setter Property="FontSize" Value="13" />
|
||||||
|
<Setter Property="MinHeight" Value="24" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
</Styles>
|
</Styles>
|
||||||
@ -7,21 +7,21 @@ using System.Net.Http;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Windows.Ninject;
|
using Artemis.UI.Windows.DryIoc;
|
||||||
using Artemis.UI.Windows.Providers.Input;
|
using Artemis.UI.Windows.Providers.Input;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Ninject;
|
using DryIoc;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Windows;
|
namespace Artemis.UI.Windows;
|
||||||
|
|
||||||
public class App : Application
|
public class App : Application
|
||||||
{
|
{
|
||||||
private StandardKernel? _kernel;
|
private IContainer? _container;
|
||||||
private bool _shutDown;
|
private bool _shutDown;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
@ -33,8 +33,8 @@ public class App : Application
|
|||||||
Environment.Exit(1);
|
Environment.Exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
_kernel = ArtemisBootstrapper.Bootstrap(this, new WindowsModule());
|
_container = ArtemisBootstrapper.Bootstrap(this, c => c.RegisterProviders());
|
||||||
Program.CreateLogger(_kernel);
|
Program.CreateLogger(_container);
|
||||||
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
|
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
@ -45,14 +45,14 @@ public class App : Application
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
ArtemisBootstrapper.Initialize();
|
ArtemisBootstrapper.Initialize();
|
||||||
_applicationStateManager = new ApplicationStateManager(_kernel!, desktop.Args);
|
_applicationStateManager = new ApplicationStateManager(_container!, desktop.Args);
|
||||||
RegisterProviders(_kernel!);
|
RegisterProviders(_container!);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RegisterProviders(StandardKernel standardKernel)
|
private void RegisterProviders(IContainer container)
|
||||||
{
|
{
|
||||||
IInputService inputService = standardKernel.Get<IInputService>();
|
IInputService inputService = container.Resolve<IInputService>();
|
||||||
inputService.AddInputProvider(standardKernel.Get<WindowsInputProvider>());
|
inputService.AddInputProvider(container.Resolve<InputProvider>(serviceKey: WindowsInputProvider.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool FocusExistingInstance()
|
private bool FocusExistingInstance()
|
||||||
|
|||||||
@ -10,35 +10,36 @@ using Artemis.UI.Windows.Utilities;
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Ninject;
|
using DryIoc;
|
||||||
|
|
||||||
namespace Artemis.UI.Windows;
|
namespace Artemis.UI.Windows;
|
||||||
|
|
||||||
public class ApplicationStateManager
|
public class ApplicationStateManager
|
||||||
{
|
{
|
||||||
private const int SM_SHUTTINGDOWN = 0x2000;
|
private const int SM_SHUTTINGDOWN = 0x2000;
|
||||||
|
|
||||||
public ApplicationStateManager(IKernel kernel, string[] startupArguments)
|
public ApplicationStateManager(IContainer container, string[] startupArguments)
|
||||||
{
|
{
|
||||||
StartupArguments = startupArguments;
|
StartupArguments = startupArguments;
|
||||||
IsElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
|
IsElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
|
||||||
|
|
||||||
Core.Utilities.ShutdownRequested += UtilitiesOnShutdownRequested;
|
Core.Utilities.ShutdownRequested += UtilitiesOnShutdownRequested;
|
||||||
Core.Utilities.RestartRequested += UtilitiesOnRestartRequested;
|
Core.Utilities.RestartRequested += UtilitiesOnRestartRequested;
|
||||||
|
Core.Utilities.UpdateRequested += UtilitiesOnUpdateRequested;
|
||||||
|
|
||||||
// On Windows shutdown dispose the kernel just so device providers get a chance to clean up
|
// On Windows shutdown dispose the IOC container just so device providers get a chance to clean up
|
||||||
if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime)
|
if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime)
|
||||||
controlledApplicationLifetime.Exit += (_, _) =>
|
controlledApplicationLifetime.Exit += (_, _) =>
|
||||||
{
|
{
|
||||||
RunForcedShutdownIfEnabled();
|
RunForcedShutdownIfEnabled();
|
||||||
|
|
||||||
// Dispose plugins before disposing the kernel because plugins might access services during dispose
|
// Dispose plugins before disposing the IOC container because plugins might access services during dispose
|
||||||
kernel.Get<IPluginManagementService>().Dispose();
|
container.Resolve<IPluginManagementService>().Dispose();
|
||||||
kernel.Dispose();
|
container.Dispose();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Inform the Core about elevation status
|
// Inform the Core about elevation status
|
||||||
kernel.Get<ICoreService>().IsElevated = IsElevated;
|
container.Resolve<ICoreService>().IsElevated = IsElevated;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string[] StartupArguments { get; }
|
public string[] StartupArguments { get; }
|
||||||
@ -91,6 +92,33 @@ public class ApplicationStateManager
|
|||||||
Dispatcher.UIThread.Post(() => controlledApplicationLifetime.Shutdown());
|
Dispatcher.UIThread.Post(() => controlledApplicationLifetime.Shutdown());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UtilitiesOnUpdateRequested(object? sender, UpdateEventArgs e)
|
||||||
|
{
|
||||||
|
List<string> argsList = new(StartupArguments);
|
||||||
|
if (e.Silent)
|
||||||
|
argsList.Add("--autorun");
|
||||||
|
|
||||||
|
// Retain startup arguments after update by providing them to the script
|
||||||
|
string script = $"\"{Path.Combine(Constants.DataFolder, "updating", "pending", "scripts", "update.ps1")}\"";
|
||||||
|
string source = $"-sourceDirectory \"{Path.Combine(Constants.DataFolder, "updating", "pending")}\"";
|
||||||
|
string destination = $"-destinationDirectory \"{Constants.ApplicationFolder}\"";
|
||||||
|
string args = argsList.Any() ? $"-artemisArgs \"{string.Join(',', argsList)}\"" : "";
|
||||||
|
|
||||||
|
// Run the PowerShell script included in the new version, that way any changes made to the script are used
|
||||||
|
ProcessStartInfo info = new()
|
||||||
|
{
|
||||||
|
Arguments = $"-File {script} {source} {destination} {args}",
|
||||||
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
FileName = "PowerShell.exe"
|
||||||
|
};
|
||||||
|
Process.Start(info);
|
||||||
|
|
||||||
|
// Lets try a graceful shutdown, PowerShell will kill if needed
|
||||||
|
if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime)
|
||||||
|
Dispatcher.UIThread.Post(() => controlledApplicationLifetime.Shutdown());
|
||||||
|
}
|
||||||
|
|
||||||
private void UtilitiesOnShutdownRequested(object? sender, EventArgs e)
|
private void UtilitiesOnShutdownRequested(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
// Use PowerShell to kill the process after 8 sec just in case
|
// Use PowerShell to kill the process after 8 sec just in case
|
||||||
@ -115,7 +143,7 @@ public class ApplicationStateManager
|
|||||||
};
|
};
|
||||||
Process.Start(info);
|
Process.Start(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
[System.Runtime.InteropServices.DllImport("user32.dll")]
|
[System.Runtime.InteropServices.DllImport("user32.dll")]
|
||||||
private static extern int GetSystemMetrics(int nIndex);
|
private static extern int GetSystemMetrics(int nIndex);
|
||||||
}
|
}
|
||||||
@ -12,20 +12,10 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AvaloniaResource Include="Assets\**" />
|
<AvaloniaResource Include="Assets\**" />
|
||||||
<None Remove=".gitignore" />
|
|
||||||
<None Remove="Assets\Cursors\aero_crosshair.cur" />
|
<Content Include="Scripts\**">
|
||||||
<None Remove="Assets\Cursors\aero_crosshair_minus.cur" />
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<None Remove="Assets\Cursors\aero_crosshair_plus.cur" />
|
</Content>
|
||||||
<None Remove="Assets\Cursors\aero_drag.cur" />
|
|
||||||
<None Remove="Assets\Cursors\aero_drag_ew.cur" />
|
|
||||||
<None Remove="Assets\Cursors\aero_fill.cur" />
|
|
||||||
<None Remove="Assets\Cursors\aero_pen_min.cur" />
|
|
||||||
<None Remove="Assets\Cursors\aero_pen_plus.cur" />
|
|
||||||
<None Remove="Assets\Cursors\aero_rotate.cur" />
|
|
||||||
<None Remove="Assets\Cursors\aero_rotate_bl.cur" />
|
|
||||||
<None Remove="Assets\Cursors\aero_rotate_br.cur" />
|
|
||||||
<None Remove="Assets\Cursors\aero_rotate_tl.cur" />
|
|
||||||
<None Remove="Assets\Cursors\aero_rotate_tr.cur" />
|
|
||||||
<None Include="..\Artemis.UI\Assets\Images\Logo\application.ico">
|
<None Include="..\Artemis.UI\Assets\Images\Logo\application.ico">
|
||||||
<Link>application.ico</Link>
|
<Link>application.ico</Link>
|
||||||
</None>
|
</None>
|
||||||
|
|||||||
26
src/Artemis.UI.Windows/DryIoc/ContainerExtensions.cs
Normal file
26
src/Artemis.UI.Windows/DryIoc/ContainerExtensions.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using Artemis.Core.Providers;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.UI.Shared.Providers;
|
||||||
|
using Artemis.UI.Windows.Providers;
|
||||||
|
using Artemis.UI.Windows.Providers.Input;
|
||||||
|
using DryIoc;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Windows.DryIoc;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides an extension method to register services onto a DryIoc <see cref="IContainer"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class UIContainerExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Registers providers into the container.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="container">The builder building the current container</param>
|
||||||
|
public static void RegisterProviders(this IContainer container)
|
||||||
|
{
|
||||||
|
container.Register<ICursorProvider, CursorProvider>(Reuse.Singleton);
|
||||||
|
container.Register<IGraphicsContextProvider, GraphicsContextProvider>(Reuse.Singleton);
|
||||||
|
container.Register<IAutoRunProvider, AutoRunProvider>();
|
||||||
|
container.Register<InputProvider, WindowsInputProvider>(serviceKey: WindowsInputProvider.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,22 +0,0 @@
|
|||||||
using Artemis.Core.Providers;
|
|
||||||
using Artemis.UI.Shared.Providers;
|
|
||||||
using Artemis.UI.Windows.Providers;
|
|
||||||
using Ninject.Modules;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Windows.Ninject;
|
|
||||||
|
|
||||||
public class WindowsModule : NinjectModule
|
|
||||||
{
|
|
||||||
#region Overrides of NinjectModule
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Load()
|
|
||||||
{
|
|
||||||
Kernel!.Bind<ICursorProvider>().To<CursorProvider>().InSingletonScope();
|
|
||||||
Kernel!.Bind<IGraphicsContextProvider>().To<GraphicsContextProvider>().InSingletonScope();
|
|
||||||
Kernel!.Bind<IUpdateProvider>().To<UpdateProvider>().InSingletonScope();
|
|
||||||
Kernel!.Bind<IAutoRunProvider>().To<AutoRunProvider>();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.Storage;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using Ninject;
|
using DryIoc;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Artemis.UI.Windows;
|
namespace Artemis.UI.Windows;
|
||||||
@ -18,6 +20,7 @@ internal class Program
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
StorageManager.CreateBackup(Constants.DataFolder);
|
||||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@ -41,8 +44,8 @@ internal class Program
|
|||||||
.UseReactiveUI();
|
.UseReactiveUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CreateLogger(IKernel kernel)
|
public static void CreateLogger(IContainer container)
|
||||||
{
|
{
|
||||||
Logger = kernel.Get<ILogger>().ForContext<Program>();
|
Logger = container.Resolve<ILogger>().ForContext<Program>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -37,6 +37,8 @@ public class WindowsInputProvider : InputProvider
|
|||||||
RawInputDevice.RegisterDevice(HidUsageAndPage.Mouse, RawInputDeviceFlags.InputSink, _sponge.Handle.Handle);
|
RawInputDevice.RegisterDevice(HidUsageAndPage.Mouse, RawInputDeviceFlags.InputSink, _sponge.Handle.Handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Guid Id { get; } = new("6737b204-ffb1-4cd9-8776-9fb851db303a");
|
||||||
|
|
||||||
|
|
||||||
#region Overrides of InputProvider
|
#region Overrides of InputProvider
|
||||||
|
|
||||||
|
|||||||
@ -1,213 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Artemis.Core;
|
|
||||||
using Artemis.UI.Exceptions;
|
|
||||||
using Artemis.UI.Shared.Providers;
|
|
||||||
using Artemis.UI.Shared.Services;
|
|
||||||
using Artemis.UI.Shared.Services.MainWindow;
|
|
||||||
using Artemis.UI.Windows.Models;
|
|
||||||
using Artemis.UI.Windows.Screens.Update;
|
|
||||||
using Avalonia.Threading;
|
|
||||||
using Flurl;
|
|
||||||
using Flurl.Http;
|
|
||||||
using Microsoft.Toolkit.Uwp.Notifications;
|
|
||||||
using Serilog;
|
|
||||||
using File = System.IO.File;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Windows.Providers;
|
|
||||||
|
|
||||||
public class UpdateProvider : IUpdateProvider, IDisposable
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
private readonly IWindowService _windowService;
|
|
||||||
|
|
||||||
public UpdateProvider(ILogger logger, IWindowService windowService, IMainWindowService mainWindowService)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_windowService = windowService;
|
|
||||||
_mainWindowService = mainWindowService;
|
|
||||||
|
|
||||||
ToastNotificationManagerCompat.OnActivated += ToastNotificationManagerCompatOnOnActivated;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<DevOpsBuild?> GetBuildInfo(int buildDefinition, string? buildNumber = null)
|
|
||||||
{
|
|
||||||
Url request = API_URL.AppendPathSegments("build", "builds")
|
|
||||||
.SetQueryParam("definitions", buildDefinition)
|
|
||||||
.SetQueryParam("resultFilter", "succeeded")
|
|
||||||
.SetQueryParam("branchName", "refs/heads/master")
|
|
||||||
.SetQueryParam("$top", 1)
|
|
||||||
.SetQueryParam("api-version", "6.1-preview.6");
|
|
||||||
|
|
||||||
if (buildNumber != null)
|
|
||||||
request = request.SetQueryParam("buildNumber", buildNumber);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
DevOpsBuilds result = await request.GetJsonAsync<DevOpsBuilds>();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return result.Builds.FirstOrDefault();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.Warning(e, "GetBuildInfo: Failed to retrieve build info JSON");
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (FlurlHttpException e)
|
|
||||||
{
|
|
||||||
_logger.Warning("GetBuildInfo: Getting build info, request returned {StatusCode}", e.StatusCode);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<GitHubDifference> GetBuildDifferences(DevOpsBuild a, DevOpsBuild b)
|
|
||||||
{
|
|
||||||
return await "https://api.github.com"
|
|
||||||
.AppendPathSegments("repos", "Artemis-RGB", "Artemis", "compare")
|
|
||||||
.AppendPathSegment(a.SourceVersion + "..." + b.SourceVersion)
|
|
||||||
.WithHeader("User-Agent", "Artemis 2")
|
|
||||||
.WithHeader("Accept", "application/vnd.github.v3+json")
|
|
||||||
.GetJsonAsync<GitHubDifference>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void ToastNotificationManagerCompatOnOnActivated(ToastNotificationActivatedEventArgsCompat e)
|
|
||||||
{
|
|
||||||
ToastArguments args = ToastArguments.Parse(e.Argument);
|
|
||||||
string channel = args.Get("channel");
|
|
||||||
string action = "view-changes";
|
|
||||||
if (args.Contains("action"))
|
|
||||||
action = args.Get("action");
|
|
||||||
|
|
||||||
if (action == "install")
|
|
||||||
await RunInstaller(channel, true);
|
|
||||||
else if (action == "view-changes")
|
|
||||||
await Dispatcher.UIThread.InvokeAsync(async () =>
|
|
||||||
{
|
|
||||||
_mainWindowService.OpenMainWindow();
|
|
||||||
await OfferUpdate(channel, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RunInstaller(string channel, bool silent)
|
|
||||||
{
|
|
||||||
_logger.Information("ApplyUpdate: Applying update");
|
|
||||||
|
|
||||||
// Ensure the installer is up-to-date, get installer build info
|
|
||||||
DevOpsBuild? buildInfo = await GetBuildInfo(6);
|
|
||||||
string installerPath = Path.Combine(Constants.DataFolder, "installer", "Artemis.Installer.exe");
|
|
||||||
|
|
||||||
// Always update installer if it is missing ^^
|
|
||||||
if (!File.Exists(installerPath))
|
|
||||||
{
|
|
||||||
await UpdateInstaller();
|
|
||||||
}
|
|
||||||
// Compare the creation date of the installer with the build date and update if needed
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (buildInfo != null && File.GetLastWriteTime(installerPath) < buildInfo.FinishTime)
|
|
||||||
await UpdateInstaller();
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.Information("ApplyUpdate: Running installer at {InstallerPath}", installerPath);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Process.Start(new ProcessStartInfo(installerPath, "-autoupdate")
|
|
||||||
{
|
|
||||||
UseShellExecute = true,
|
|
||||||
Verb = "runas"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Win32Exception e)
|
|
||||||
{
|
|
||||||
if (e.NativeErrorCode == 0x4c7)
|
|
||||||
_logger.Warning("ApplyUpdate: Operation was cancelled, user likely clicked No in UAC dialog");
|
|
||||||
else
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UpdateInstaller()
|
|
||||||
{
|
|
||||||
string installerDirectory = Path.Combine(Constants.DataFolder, "installer");
|
|
||||||
string installerPath = Path.Combine(installerDirectory, "Artemis.Installer.exe");
|
|
||||||
|
|
||||||
_logger.Information("UpdateInstaller: Downloading installer from {DownloadUrl}", INSTALLER_URL);
|
|
||||||
using HttpClient client = new();
|
|
||||||
HttpResponseMessage httpResponseMessage = await client.GetAsync(INSTALLER_URL);
|
|
||||||
if (!httpResponseMessage.IsSuccessStatusCode)
|
|
||||||
throw new ArtemisUIException($"Failed to download installer, status code {httpResponseMessage.StatusCode}");
|
|
||||||
|
|
||||||
_logger.Information("UpdateInstaller: Writing installer file to {InstallerPath}", installerPath);
|
|
||||||
if (File.Exists(installerPath))
|
|
||||||
File.Delete(installerPath);
|
|
||||||
|
|
||||||
Core.Utilities.CreateAccessibleDirectory(installerDirectory);
|
|
||||||
await using FileStream fs = new(installerPath, FileMode.Create, FileAccess.Write, FileShare.None);
|
|
||||||
await httpResponseMessage.Content.CopyToAsync(fs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ShowDesktopNotification(string channel)
|
|
||||||
{
|
|
||||||
new ToastContentBuilder()
|
|
||||||
.AddArgument("channel", channel)
|
|
||||||
.AddText("An update is available")
|
|
||||||
.AddButton(new ToastButton().SetContent("Install").AddArgument("action", "install").SetBackgroundActivation())
|
|
||||||
.AddButton(new ToastButton().SetContent("View changes").AddArgument("action", "view-changes"))
|
|
||||||
.Show();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
ToastNotificationManagerCompat.OnActivated -= ToastNotificationManagerCompatOnOnActivated;
|
|
||||||
ToastNotificationManagerCompat.Uninstall();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public async Task<bool> CheckForUpdate(string channel)
|
|
||||||
{
|
|
||||||
DevOpsBuild? buildInfo = await GetBuildInfo(1);
|
|
||||||
if (buildInfo == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
double buildNumber = double.Parse(buildInfo.BuildNumber, CultureInfo.InvariantCulture);
|
|
||||||
string buildNumberDisplay = buildNumber.ToString(CultureInfo.InvariantCulture);
|
|
||||||
_logger.Information("Latest build is {BuildNumber}, we're running {LocalBuildNumber}", buildNumberDisplay, Constants.BuildInfo.BuildNumberDisplay);
|
|
||||||
|
|
||||||
return buildNumber > Constants.BuildInfo.BuildNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public async Task ApplyUpdate(string channel, bool silent)
|
|
||||||
{
|
|
||||||
await RunInstaller(channel, silent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public async Task OfferUpdate(string channel, bool windowOpen)
|
|
||||||
{
|
|
||||||
if (windowOpen)
|
|
||||||
{
|
|
||||||
bool update = await _windowService.ShowDialogAsync<UpdateDialogViewModel, bool>(("channel", channel));
|
|
||||||
if (update)
|
|
||||||
await RunInstaller(channel, false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ShowDesktopNotification(channel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
<Window xmlns="https://github.com/avaloniaui"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:update="clr-namespace:Artemis.UI.Windows.Screens.Update"
|
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
|
||||||
x:Class="Artemis.UI.Windows.Screens.Update.UpdateDialogView"
|
|
||||||
x:DataType="update:UpdateDialogViewModel"
|
|
||||||
Title="Artemis | Update available"
|
|
||||||
Width="750"
|
|
||||||
MinWidth="750"
|
|
||||||
Height="500"
|
|
||||||
WindowStartupLocation="CenterOwner">
|
|
||||||
<Grid Margin="15" RowDefinitions="Auto,*,Auto">
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="0" Classes="h4">
|
|
||||||
A new Artemis update is available! 🥳
|
|
||||||
</TextBlock>
|
|
||||||
|
|
||||||
<StackPanel Grid.Row="1" IsVisible="{CompiledBinding RetrievingChanges}" VerticalAlignment="Center" HorizontalAlignment="Center">
|
|
||||||
<TextBlock>Retrieving changes...</TextBlock>
|
|
||||||
<ProgressBar IsIndeterminate="True"></ProgressBar>
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<Border Grid.Row="1" Classes="card" IsVisible="{CompiledBinding !RetrievingChanges}">
|
|
||||||
<Grid RowDefinitions="Auto,*">
|
|
||||||
<StackPanel Grid.Row="0">
|
|
||||||
<StackPanel Orientation="Horizontal">
|
|
||||||
<TextBlock Text="You are currently running build " />
|
|
||||||
<TextBlock Text="{CompiledBinding CurrentBuild, Mode=OneWay}"></TextBlock>
|
|
||||||
<TextBlock Text=" while the latest build is " />
|
|
||||||
<TextBlock Text="{CompiledBinding LatestBuild, Mode=OneWay}"></TextBlock>
|
|
||||||
<TextBlock Text="." />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<TextBlock Text="Updating Artemis will give you the latest bug(fixes), features and improvements." />
|
|
||||||
<Separator Classes="card-separator" />
|
|
||||||
|
|
||||||
<TextBlock Classes="h5" IsVisible="{CompiledBinding HasChanges}">
|
|
||||||
Changelog (auto-generated)
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<ScrollViewer Grid.Row="1" VerticalAlignment="Top" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" IsVisible="{CompiledBinding HasChanges}">
|
|
||||||
<ItemsControl Items="{CompiledBinding Changes}">
|
|
||||||
<ItemsControl.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<Grid ColumnDefinitions="Auto,*">
|
|
||||||
<TextBlock Text="•" Margin="10 0" />
|
|
||||||
<TextBlock Grid.Column="1" Text="{Binding}" TextWrapping="Wrap" />
|
|
||||||
</Grid>
|
|
||||||
</DataTemplate>
|
|
||||||
</ItemsControl.ItemTemplate>
|
|
||||||
</ItemsControl>
|
|
||||||
</ScrollViewer>
|
|
||||||
|
|
||||||
<StackPanel Grid.Row="1" IsVisible="{CompiledBinding !HasChanges}" VerticalAlignment="Center" HorizontalAlignment="Center">
|
|
||||||
<TextBlock HorizontalAlignment="Center">We couldn't retrieve any changes</TextBlock>
|
|
||||||
<controls:HyperlinkButton NavigateUri="https://github.com/Artemis-RGB/Artemis/commits/master" HorizontalAlignment="Center">View online</controls:HyperlinkButton>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" Spacing="5" Grid.Row="2" Margin="0 15 0 0">
|
|
||||||
<Button Classes="accent" Command="{CompiledBinding Install}">Install update</Button>
|
|
||||||
<Button Command="{CompiledBinding AskLater}">Ask later</Button>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
</Window>
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
using Artemis.UI.Shared;
|
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Windows.Screens.Update;
|
|
||||||
|
|
||||||
public class UpdateDialogView : ReactiveCoreWindow<UpdateDialogViewModel>
|
|
||||||
{
|
|
||||||
public UpdateDialogView()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
#if DEBUG
|
|
||||||
this.AttachDevTools();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeComponent()
|
|
||||||
{
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,120 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reactive;
|
|
||||||
using System.Reactive.Disposables;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Artemis.Core;
|
|
||||||
using Artemis.UI.Shared;
|
|
||||||
using Artemis.UI.Shared.Providers;
|
|
||||||
using Artemis.UI.Shared.Services;
|
|
||||||
using Artemis.UI.Shared.Services.Builders;
|
|
||||||
using Artemis.UI.Windows.Models;
|
|
||||||
using Artemis.UI.Windows.Providers;
|
|
||||||
using Avalonia.Threading;
|
|
||||||
using DynamicData;
|
|
||||||
using ReactiveUI;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Windows.Screens.Update;
|
|
||||||
|
|
||||||
public class UpdateDialogViewModel : DialogViewModelBase<bool>
|
|
||||||
{
|
|
||||||
// Based on https://docs.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#skipping-ci-for-individual-commits
|
|
||||||
private readonly string[] _excludedCommitMessages =
|
|
||||||
{
|
|
||||||
"[skip ci]",
|
|
||||||
"[ci skip]",
|
|
||||||
"skip-checks: true",
|
|
||||||
"skip-checks:true",
|
|
||||||
"[skip azurepipelines]",
|
|
||||||
"[azurepipelines skip]",
|
|
||||||
"[skip azpipelines]",
|
|
||||||
"[azpipelines skip]",
|
|
||||||
"[skip azp]",
|
|
||||||
"[azp skip]",
|
|
||||||
"***NO_CI***"
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly INotificationService _notificationService;
|
|
||||||
private readonly UpdateProvider _updateProvider;
|
|
||||||
private bool _hasChanges;
|
|
||||||
private string? _latestBuild;
|
|
||||||
|
|
||||||
private bool _retrievingChanges;
|
|
||||||
|
|
||||||
public UpdateDialogViewModel(string channel, IUpdateProvider updateProvider, INotificationService notificationService)
|
|
||||||
{
|
|
||||||
_updateProvider = (UpdateProvider) updateProvider;
|
|
||||||
_notificationService = notificationService;
|
|
||||||
|
|
||||||
Channel = channel;
|
|
||||||
CurrentBuild = Constants.BuildInfo.BuildNumberDisplay;
|
|
||||||
|
|
||||||
this.WhenActivated((CompositeDisposable _) => Dispatcher.UIThread.InvokeAsync(GetBuildChanges));
|
|
||||||
Install = ReactiveCommand.Create(() => Close(true));
|
|
||||||
AskLater = ReactiveCommand.Create(() => Close(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> Install { get; }
|
|
||||||
public ReactiveCommand<Unit, Unit> AskLater { get; }
|
|
||||||
|
|
||||||
public string Channel { get; }
|
|
||||||
public string CurrentBuild { get; }
|
|
||||||
|
|
||||||
public ObservableCollection<string> Changes { get; } = new();
|
|
||||||
|
|
||||||
public bool RetrievingChanges
|
|
||||||
{
|
|
||||||
get => _retrievingChanges;
|
|
||||||
set => RaiseAndSetIfChanged(ref _retrievingChanges, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HasChanges
|
|
||||||
{
|
|
||||||
get => _hasChanges;
|
|
||||||
set => RaiseAndSetIfChanged(ref _hasChanges, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string? LatestBuild
|
|
||||||
{
|
|
||||||
get => _latestBuild;
|
|
||||||
set => RaiseAndSetIfChanged(ref _latestBuild, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task GetBuildChanges()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
RetrievingChanges = true;
|
|
||||||
Task<DevOpsBuild?> currentTask = _updateProvider.GetBuildInfo(1, CurrentBuild);
|
|
||||||
Task<DevOpsBuild?> latestTask = _updateProvider.GetBuildInfo(1);
|
|
||||||
|
|
||||||
DevOpsBuild? current = await currentTask;
|
|
||||||
DevOpsBuild? latest = await latestTask;
|
|
||||||
|
|
||||||
LatestBuild = latest?.BuildNumber;
|
|
||||||
if (current != null && latest != null)
|
|
||||||
{
|
|
||||||
GitHubDifference difference = await _updateProvider.GetBuildDifferences(current, latest);
|
|
||||||
|
|
||||||
// Only take commits with one parents (no merges)
|
|
||||||
Changes.Clear();
|
|
||||||
Changes.AddRange(difference.Commits.Where(c => c.Parents.Count == 1)
|
|
||||||
.SelectMany(c => c.Commit.Message.Split("\n"))
|
|
||||||
.Select(m => m.Trim())
|
|
||||||
.Where(m => !string.IsNullOrWhiteSpace(m) && !_excludedCommitMessages.Contains(m))
|
|
||||||
.OrderBy(m => m)
|
|
||||||
);
|
|
||||||
HasChanges = Changes.Any();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_notificationService.CreateNotification().WithTitle("Failed to retrieve build changes").WithMessage(e.Message).WithSeverity(NotificationSeverity.Error).Show();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
RetrievingChanges = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
42
src/Artemis.UI.Windows/Scripts/update.ps1
Normal file
42
src/Artemis.UI.Windows/Scripts/update.ps1
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
param (
|
||||||
|
[Parameter(Mandatory=$true)][string]$sourceDirectory,
|
||||||
|
[Parameter(Mandatory=$true)][string]$destinationDirectory,
|
||||||
|
[Parameter(Mandatory=$false)][string]$artemisArgs
|
||||||
|
)
|
||||||
|
|
||||||
|
# Wait up to 10 seconds for the process to shut down
|
||||||
|
for ($i=1; $i -le 10; $i++) {
|
||||||
|
$process = Get-Process -Name Artemis.UI.Windows -ErrorAction SilentlyContinue
|
||||||
|
if (!$process) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
Write-Host "Waiting for Artemis to shut down ($i / 10)"
|
||||||
|
Start-Sleep -Seconds 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# If the process is still running, kill it
|
||||||
|
$process = Get-Process -Name Artemis.UI.Windows -ErrorAction SilentlyContinue
|
||||||
|
if ($process) {
|
||||||
|
Stop-Process -Id $process.Id -Force
|
||||||
|
Start-Sleep -Seconds 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if the destination directory exists
|
||||||
|
if (!(Test-Path $destinationDirectory)) {
|
||||||
|
Write-Error "The destination directory does not exist"
|
||||||
|
}
|
||||||
|
|
||||||
|
# If the destination directory exists, clear it
|
||||||
|
Get-ChildItem $destinationDirectory | Remove-Item -Recurse -Force
|
||||||
|
|
||||||
|
# Move the contents of the source directory to the destination directory
|
||||||
|
Get-ChildItem $sourceDirectory | Move-Item -Destination $destinationDirectory
|
||||||
|
|
||||||
|
Start-Sleep -Seconds 1
|
||||||
|
|
||||||
|
# When finished, run the updated version
|
||||||
|
if ($artemisArgs) {
|
||||||
|
Start-Process -FilePath "$destinationDirectory\Artemis.UI.Windows.exe" -WorkingDirectory $destinationDirectory -ArgumentList $artemisArgs
|
||||||
|
} else {
|
||||||
|
Start-Process -FilePath "$destinationDirectory\Artemis.UI.Windows.exe" -WorkingDirectory $destinationDirectory
|
||||||
|
}
|
||||||
@ -11,10 +11,12 @@
|
|||||||
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
|
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
|
||||||
<ProjectReference Include="..\Artemis.UI.Shared\Artemis.UI.Shared.csproj" />
|
<ProjectReference Include="..\Artemis.UI.Shared\Artemis.UI.Shared.csproj" />
|
||||||
<ProjectReference Include="..\Artemis.VisualScripting\Artemis.VisualScripting.csproj" />
|
<ProjectReference Include="..\Artemis.VisualScripting\Artemis.VisualScripting.csproj" />
|
||||||
|
<ProjectReference Include="..\Artemis.WebClient.Updating\Artemis.WebClient.Updating.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia" Version="0.10.18" />
|
<PackageReference Include="Avalonia" Version="0.10.18" />
|
||||||
|
<PackageReference Include="Avalonia.AvaloniaEdit" Version="0.10.12.2" />
|
||||||
<PackageReference Include="Avalonia.Controls.PanAndZoom" Version="10.14.0" />
|
<PackageReference Include="Avalonia.Controls.PanAndZoom" Version="10.14.0" />
|
||||||
<PackageReference Include="Avalonia.Controls.Skia" Version="0.10.16" />
|
<PackageReference Include="Avalonia.Controls.Skia" Version="0.10.16" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
|
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
|
||||||
@ -22,17 +24,20 @@
|
|||||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.18" />
|
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.18" />
|
||||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.18" />
|
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.18" />
|
||||||
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.18" />
|
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.18" />
|
||||||
|
<PackageReference Include="DryIoc.dll" Version="5.3.1" />
|
||||||
<PackageReference Include="DynamicData" Version="7.9.14" />
|
<PackageReference Include="DynamicData" Version="7.9.14" />
|
||||||
<PackageReference Include="FluentAvaloniaUI" Version="1.4.1" />
|
<PackageReference Include="FluentAvaloniaUI" Version="1.4.1" />
|
||||||
<PackageReference Include="Flurl.Http" Version="3.2.4" />
|
<PackageReference Include="Flurl.Http" Version="3.2.4" />
|
||||||
<PackageReference Include="Live.Avalonia" Version="1.3.1" />
|
<PackageReference Include="Live.Avalonia" Version="1.3.1" />
|
||||||
|
<PackageReference Include="Markdown.Avalonia.Tight" Version="0.10.13" />
|
||||||
<PackageReference Include="Material.Icons.Avalonia" Version="1.1.10" />
|
<PackageReference Include="Material.Icons.Avalonia" Version="1.1.10" />
|
||||||
|
<PackageReference Include="Octopus.Octodiff" Version="2.0.100" />
|
||||||
<PackageReference Include="ReactiveUI" Version="17.1.50" />
|
<PackageReference Include="ReactiveUI" Version="17.1.50" />
|
||||||
<PackageReference Include="ReactiveUI.Validation" Version="2.2.1" />
|
<PackageReference Include="ReactiveUI.Validation" Version="2.2.1" />
|
||||||
<PackageReference Include="RGB.NET.Core" Version="1.0.0" />
|
<PackageReference Include="RGB.NET.Core" Version="1.0.0" />
|
||||||
<PackageReference Include="RGB.NET.Layout" Version="1.0.0" />
|
<PackageReference Include="RGB.NET.Layout" Version="1.0.0" />
|
||||||
<PackageReference Include="SkiaSharp" Version="2.88.1-preview.108" />
|
<PackageReference Include="SkiaSharp" Version="2.88.1-preview.108" />
|
||||||
<PackageReference Include="Splat.Ninject" Version="14.4.1" />
|
<PackageReference Include="Splat.DryIoc" Version="14.6.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -3,53 +3,56 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Ninject;
|
using Artemis.Core.DryIoc;
|
||||||
|
using Artemis.UI.DryIoc;
|
||||||
using Artemis.UI.Exceptions;
|
using Artemis.UI.Exceptions;
|
||||||
using Artemis.UI.Ninject;
|
|
||||||
using Artemis.UI.Screens.Root;
|
using Artemis.UI.Screens.Root;
|
||||||
using Artemis.UI.Shared.DataModelPicker;
|
using Artemis.UI.Shared.DataModelPicker;
|
||||||
using Artemis.UI.Shared.Ninject;
|
using Artemis.UI.Shared.DryIoc;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using Artemis.VisualScripting.Ninject;
|
using Artemis.VisualScripting.DryIoc;
|
||||||
|
using Artemis.WebClient.Updating.DryIoc;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Ninject;
|
using DryIoc;
|
||||||
using Ninject.Modules;
|
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using Splat.Ninject;
|
using Splat.DryIoc;
|
||||||
|
|
||||||
namespace Artemis.UI;
|
namespace Artemis.UI;
|
||||||
|
|
||||||
public static class ArtemisBootstrapper
|
public static class ArtemisBootstrapper
|
||||||
{
|
{
|
||||||
private static StandardKernel? _kernel;
|
private static Container? _container;
|
||||||
private static Application? _application;
|
private static Application? _application;
|
||||||
|
|
||||||
public static StandardKernel Bootstrap(Application application, params INinjectModule[] modules)
|
public static IContainer Bootstrap(Application application, Action<IContainer>? configureServices = null)
|
||||||
{
|
{
|
||||||
if (_application != null || _kernel != null)
|
if (_application != null || _container != null)
|
||||||
throw new ArtemisUIException("UI already bootstrapped");
|
throw new ArtemisUIException("UI already bootstrapped");
|
||||||
|
|
||||||
Utilities.PrepareFirstLaunch();
|
Utilities.PrepareFirstLaunch();
|
||||||
|
|
||||||
_application = application;
|
_application = application;
|
||||||
_kernel = new StandardKernel();
|
_container = new Container(rules => rules
|
||||||
_kernel.Settings.InjectNonPublic = true;
|
.WithMicrosoftDependencyInjectionRules()
|
||||||
|
.WithConcreteTypeDynamicRegistrations()
|
||||||
|
.WithoutThrowOnRegisteringDisposableTransient());
|
||||||
|
|
||||||
_kernel.Load<CoreModule>();
|
_container.RegisterCore();
|
||||||
_kernel.Load<UIModule>();
|
_container.RegisterUI();
|
||||||
_kernel.Load<SharedUIModule>();
|
_container.RegisterSharedUI();
|
||||||
_kernel.Load<NoStringNinjectModule>();
|
_container.RegisterUpdatingClient();
|
||||||
_kernel.Load(modules);
|
_container.RegisterNoStringEvaluating();
|
||||||
_kernel.UseNinjectDependencyResolver();
|
configureServices?.Invoke(_container);
|
||||||
|
|
||||||
return _kernel;
|
_container.UseDryIocDependencyResolver();
|
||||||
|
return _container;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Initialize()
|
public static void Initialize()
|
||||||
{
|
{
|
||||||
if (_application == null || _kernel == null)
|
if (_application == null || _container == null)
|
||||||
throw new ArtemisUIException("UI not yet bootstrapped");
|
throw new ArtemisUIException("UI not yet bootstrapped");
|
||||||
if (_application.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
|
if (_application.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
return;
|
return;
|
||||||
@ -59,16 +62,16 @@ public static class ArtemisBootstrapper
|
|||||||
// Don't shut down when the last window closes, we might still be active in the tray
|
// Don't shut down when the last window closes, we might still be active in the tray
|
||||||
desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown;
|
desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown;
|
||||||
// Create the root view model that drives the UI
|
// Create the root view model that drives the UI
|
||||||
RootViewModel rootViewModel = _kernel.Get<RootViewModel>();
|
RootViewModel rootViewModel = _container.Resolve<RootViewModel>();
|
||||||
// Apply the root view model to the data context of the application so that tray icon commands work
|
// Apply the root view model to the data context of the application so that tray icon commands work
|
||||||
_application.DataContext = rootViewModel;
|
_application.DataContext = rootViewModel;
|
||||||
|
|
||||||
RxApp.DefaultExceptionHandler = Observer.Create<Exception>(DisplayUnhandledException);
|
RxApp.DefaultExceptionHandler = Observer.Create<Exception>(DisplayUnhandledException);
|
||||||
DataModelPicker.DataModelUIService = _kernel.Get<IDataModelUIService>();
|
DataModelPicker.DataModelUIService = _container.Resolve<IDataModelUIService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DisplayUnhandledException(Exception exception)
|
private static void DisplayUnhandledException(Exception exception)
|
||||||
{
|
{
|
||||||
_kernel?.Get<IWindowService>().ShowExceptionDialog("Exception", exception);
|
_container?.Resolve<IWindowService>().ShowExceptionDialog("Exception", exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -27,7 +27,8 @@
|
|||||||
Width="200"
|
Width="200"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Items="{CompiledBinding Descriptors}"
|
Items="{CompiledBinding Descriptors}"
|
||||||
SelectedItem="{CompiledBinding SelectedDescriptor}">
|
SelectedItem="{CompiledBinding SelectedDescriptor}"
|
||||||
|
PlaceholderText="Please select a brush">
|
||||||
<ComboBox.ItemTemplate>
|
<ComboBox.ItemTemplate>
|
||||||
<DataTemplate DataType="{x:Type layerBrushes:LayerBrushDescriptor}">
|
<DataTemplate DataType="{x:Type layerBrushes:LayerBrushDescriptor}">
|
||||||
<Grid ColumnDefinitions="30,*" RowDefinitions="Auto,Auto">
|
<Grid ColumnDefinitions="30,*" RowDefinitions="Auto,Auto">
|
||||||
|
|||||||
@ -66,7 +66,7 @@ public class BrushPropertyInputViewModel : PropertyInputViewModel<LayerBrushRefe
|
|||||||
if (layer.LayerBrush?.Presets != null && layer.LayerBrush.Presets.Any())
|
if (layer.LayerBrush?.Presets != null && layer.LayerBrush.Presets.Any())
|
||||||
Dispatcher.UIThread.InvokeAsync(() => _windowService.CreateContentDialog()
|
Dispatcher.UIThread.InvokeAsync(() => _windowService.CreateContentDialog()
|
||||||
.WithTitle("Select preset")
|
.WithTitle("Select preset")
|
||||||
.WithViewModel(out LayerBrushPresetViewModel _, ("layerBrush", layer.LayerBrush))
|
.WithViewModel(out LayerBrushPresetViewModel _, layer.LayerBrush)
|
||||||
.WithDefaultButton(ContentDialogButton.Close)
|
.WithDefaultButton(ContentDialogButton.Close)
|
||||||
.WithCloseButtonText("Use defaults")
|
.WithCloseButtonText("Use defaults")
|
||||||
.ShowAsync());
|
.ShowAsync());
|
||||||
|
|||||||
44
src/Artemis.UI/DryIoc/ContainerExtensions.cs
Normal file
44
src/Artemis.UI/DryIoc/ContainerExtensions.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using Artemis.UI.DryIoc.Factories;
|
||||||
|
using Artemis.UI.DryIoc.InstanceProviders;
|
||||||
|
using Artemis.UI.Screens;
|
||||||
|
using Artemis.UI.Screens.VisualScripting;
|
||||||
|
using Artemis.UI.Services.Interfaces;
|
||||||
|
using Artemis.UI.Services.Updating;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Services.NodeEditor;
|
||||||
|
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
|
using Avalonia.Platform;
|
||||||
|
using Avalonia.Shared.PlatformSupport;
|
||||||
|
using DryIoc;
|
||||||
|
|
||||||
|
namespace Artemis.UI.DryIoc;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides an extension method to register services onto a DryIoc <see cref="IContainer"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class UIContainerExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Registers UI services into the container.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="container">The builder building the current container</param>
|
||||||
|
public static void RegisterUI(this IContainer container)
|
||||||
|
{
|
||||||
|
Assembly[] thisAssembly = {typeof(UIContainerExtensions).Assembly};
|
||||||
|
|
||||||
|
container.RegisterInstance(new AssetLoader(), IfAlreadyRegistered.Throw);
|
||||||
|
container.Register<IAssetLoader, AssetLoader>(Reuse.Singleton);
|
||||||
|
|
||||||
|
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<ViewModelBase>());
|
||||||
|
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<MainScreenViewModel>(), ifAlreadyRegistered: IfAlreadyRegistered.Replace);
|
||||||
|
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<IToolViewModel>() && type.IsInterface);
|
||||||
|
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<IVmFactory>() && type != typeof(PropertyVmFactory));
|
||||||
|
|
||||||
|
container.Register<NodeScriptWindowViewModelBase, NodeScriptWindowViewModel>(Reuse.Singleton);
|
||||||
|
container.Register<IPropertyVmFactory, PropertyVmFactory>(Reuse.Singleton);
|
||||||
|
container.Register<IUpdateNotificationProvider, SimpleUpdateNotificationProvider>();
|
||||||
|
|
||||||
|
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<IArtemisUIService>(), Reuse.Singleton);
|
||||||
|
}
|
||||||
|
}
|
||||||
477
src/Artemis.UI/DryIoc/Factories/IVMFactory.cs
Normal file
477
src/Artemis.UI/DryIoc/Factories/IVMFactory.cs
Normal file
@ -0,0 +1,477 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Reactive;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.LayerBrushes;
|
||||||
|
using Artemis.Core.LayerEffects;
|
||||||
|
using Artemis.Core.ScriptingProviders;
|
||||||
|
using Artemis.UI.Screens.Device;
|
||||||
|
using Artemis.UI.Screens.Plugins;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor.ProfileTree;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor.Properties;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor.Properties.DataBinding;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
|
||||||
|
using Artemis.UI.Screens.Scripting;
|
||||||
|
using Artemis.UI.Screens.Settings;
|
||||||
|
using Artemis.UI.Screens.Sidebar;
|
||||||
|
using Artemis.UI.Screens.SurfaceEditor;
|
||||||
|
using Artemis.UI.Screens.VisualScripting;
|
||||||
|
using Artemis.UI.Screens.VisualScripting.Pins;
|
||||||
|
using DryIoc;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.DryIoc.Factories;
|
||||||
|
|
||||||
|
public interface IVmFactory
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IDeviceVmFactory : IVmFactory
|
||||||
|
{
|
||||||
|
DevicePropertiesViewModel DevicePropertiesViewModel(ArtemisDevice device);
|
||||||
|
DeviceSettingsViewModel DeviceSettingsViewModel(ArtemisDevice device, DevicesTabViewModel devicesTabViewModel);
|
||||||
|
DeviceDetectInputViewModel DeviceDetectInputViewModel(ArtemisDevice device);
|
||||||
|
DevicePropertiesTabViewModel DevicePropertiesTabViewModel(ArtemisDevice device);
|
||||||
|
DeviceInfoTabViewModel DeviceInfoTabViewModel(ArtemisDevice device);
|
||||||
|
DeviceLedsTabViewModel DeviceLedsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds);
|
||||||
|
InputMappingsTabViewModel InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds);
|
||||||
|
}
|
||||||
|
public class DeviceFactory : IDeviceVmFactory
|
||||||
|
{
|
||||||
|
private readonly IContainer _container;
|
||||||
|
|
||||||
|
public DeviceFactory(IContainer container)
|
||||||
|
{
|
||||||
|
_container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DevicePropertiesViewModel DevicePropertiesViewModel(ArtemisDevice device)
|
||||||
|
{
|
||||||
|
return _container.Resolve<DevicePropertiesViewModel>(new object[] { device });
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeviceSettingsViewModel DeviceSettingsViewModel(ArtemisDevice device, DevicesTabViewModel devicesTabViewModel)
|
||||||
|
{
|
||||||
|
return _container.Resolve<DeviceSettingsViewModel>(new object[] { device, devicesTabViewModel });
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeviceDetectInputViewModel DeviceDetectInputViewModel(ArtemisDevice device)
|
||||||
|
{
|
||||||
|
return _container.Resolve<DeviceDetectInputViewModel>(new object[] { device });
|
||||||
|
}
|
||||||
|
|
||||||
|
public DevicePropertiesTabViewModel DevicePropertiesTabViewModel(ArtemisDevice device)
|
||||||
|
{
|
||||||
|
return _container.Resolve<DevicePropertiesTabViewModel>(new object[] { device });
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeviceInfoTabViewModel DeviceInfoTabViewModel(ArtemisDevice device)
|
||||||
|
{
|
||||||
|
return _container.Resolve<DeviceInfoTabViewModel>(new object[] { device });
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeviceLedsTabViewModel DeviceLedsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds)
|
||||||
|
{
|
||||||
|
return _container.Resolve<DeviceLedsTabViewModel>(new object[] { device, selectedLeds });
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputMappingsTabViewModel InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds)
|
||||||
|
{
|
||||||
|
return _container.Resolve<InputMappingsTabViewModel>(new object[] { device, selectedLeds });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ISettingsVmFactory : IVmFactory
|
||||||
|
{
|
||||||
|
PluginSettingsViewModel PluginSettingsViewModel(Plugin plugin);
|
||||||
|
PluginViewModel PluginViewModel(Plugin plugin, ReactiveCommand<Unit, Unit>? reload);
|
||||||
|
PluginFeatureViewModel PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, bool showShield);
|
||||||
|
}
|
||||||
|
public class SettingsVmFactory : ISettingsVmFactory
|
||||||
|
{
|
||||||
|
private readonly IContainer _container;
|
||||||
|
|
||||||
|
public SettingsVmFactory(IContainer container)
|
||||||
|
{
|
||||||
|
_container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginSettingsViewModel PluginSettingsViewModel(Plugin plugin)
|
||||||
|
{
|
||||||
|
return _container.Resolve<PluginSettingsViewModel>(new object[] { plugin });
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginViewModel PluginViewModel(Plugin plugin, ReactiveCommand<Unit, Unit>? reload)
|
||||||
|
{
|
||||||
|
return _container.Resolve<PluginViewModel>(new object?[] { plugin, reload });
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginFeatureViewModel PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, bool showShield)
|
||||||
|
{
|
||||||
|
return _container.Resolve<PluginFeatureViewModel>(new object[] { pluginFeatureInfo, showShield });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ISidebarVmFactory : IVmFactory
|
||||||
|
{
|
||||||
|
SidebarViewModel? SidebarViewModel(IScreen hostScreen);
|
||||||
|
SidebarCategoryViewModel SidebarCategoryViewModel(ProfileCategory profileCategory);
|
||||||
|
SidebarProfileConfigurationViewModel SidebarProfileConfigurationViewModel(ProfileConfiguration profileConfiguration);
|
||||||
|
}
|
||||||
|
public class SidebarVmFactory : ISidebarVmFactory
|
||||||
|
{
|
||||||
|
private readonly IContainer _container;
|
||||||
|
|
||||||
|
public SidebarVmFactory(IContainer container)
|
||||||
|
{
|
||||||
|
_container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SidebarViewModel? SidebarViewModel(IScreen hostScreen)
|
||||||
|
{
|
||||||
|
return _container.Resolve<SidebarViewModel>(new object[] { hostScreen });
|
||||||
|
}
|
||||||
|
|
||||||
|
public SidebarCategoryViewModel SidebarCategoryViewModel(ProfileCategory profileCategory)
|
||||||
|
{
|
||||||
|
return _container.Resolve<SidebarCategoryViewModel>(new object[] { profileCategory });
|
||||||
|
}
|
||||||
|
|
||||||
|
public SidebarProfileConfigurationViewModel SidebarProfileConfigurationViewModel(ProfileConfiguration profileConfiguration)
|
||||||
|
{
|
||||||
|
return _container.Resolve<SidebarProfileConfigurationViewModel>(new object[] { profileConfiguration });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ISurfaceVmFactory : IVmFactory
|
||||||
|
{
|
||||||
|
SurfaceDeviceViewModel SurfaceDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel);
|
||||||
|
ListDeviceViewModel ListDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel);
|
||||||
|
}
|
||||||
|
public class SurfaceVmFactory : ISurfaceVmFactory
|
||||||
|
{
|
||||||
|
private readonly IContainer _container;
|
||||||
|
|
||||||
|
public SurfaceVmFactory(IContainer container)
|
||||||
|
{
|
||||||
|
_container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SurfaceDeviceViewModel SurfaceDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel)
|
||||||
|
{
|
||||||
|
return _container.Resolve<SurfaceDeviceViewModel>(new object[] { device, surfaceEditorViewModel });
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListDeviceViewModel ListDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel)
|
||||||
|
{
|
||||||
|
return _container.Resolve<ListDeviceViewModel>(new object[] { device, surfaceEditorViewModel });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IPrerequisitesVmFactory : IVmFactory
|
||||||
|
{
|
||||||
|
PluginPrerequisiteViewModel PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall);
|
||||||
|
}
|
||||||
|
public class PrerequisitesVmFactory : IPrerequisitesVmFactory
|
||||||
|
{
|
||||||
|
private readonly IContainer _container;
|
||||||
|
|
||||||
|
public PrerequisitesVmFactory(IContainer container)
|
||||||
|
{
|
||||||
|
_container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginPrerequisiteViewModel PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall)
|
||||||
|
{
|
||||||
|
return _container.Resolve<PluginPrerequisiteViewModel>(new object[] { pluginPrerequisite, uninstall });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IProfileEditorVmFactory : IVmFactory
|
||||||
|
{
|
||||||
|
ProfileEditorViewModel ProfileEditorViewModel(IScreen hostScreen);
|
||||||
|
FolderTreeItemViewModel FolderTreeItemViewModel(TreeItemViewModel? parent, Folder folder);
|
||||||
|
LayerTreeItemViewModel LayerTreeItemViewModel(TreeItemViewModel? parent, Layer layer);
|
||||||
|
LayerShapeVisualizerViewModel LayerShapeVisualizerViewModel(Layer layer);
|
||||||
|
LayerVisualizerViewModel LayerVisualizerViewModel(Layer layer);
|
||||||
|
}
|
||||||
|
public class ProfileEditorVmFactory : IProfileEditorVmFactory
|
||||||
|
{
|
||||||
|
private readonly IContainer _container;
|
||||||
|
|
||||||
|
public ProfileEditorVmFactory(IContainer container)
|
||||||
|
{
|
||||||
|
_container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FolderTreeItemViewModel FolderTreeItemViewModel(TreeItemViewModel? parent, Folder folder)
|
||||||
|
{
|
||||||
|
return _container.Resolve<FolderTreeItemViewModel>(new object?[] { parent, folder });
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayerShapeVisualizerViewModel LayerShapeVisualizerViewModel(Layer layer)
|
||||||
|
{
|
||||||
|
return _container.Resolve<LayerShapeVisualizerViewModel>(new object[] { layer });
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayerTreeItemViewModel LayerTreeItemViewModel(TreeItemViewModel? parent, Layer layer)
|
||||||
|
{
|
||||||
|
return _container.Resolve<LayerTreeItemViewModel>(new object?[] { parent, layer });
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayerVisualizerViewModel LayerVisualizerViewModel(Layer layer)
|
||||||
|
{
|
||||||
|
return _container.Resolve<LayerVisualizerViewModel>(new object[] { layer });
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProfileEditorViewModel ProfileEditorViewModel(IScreen hostScreen)
|
||||||
|
{
|
||||||
|
return _container.Resolve<ProfileEditorViewModel>(new object[] { hostScreen });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ILayerPropertyVmFactory : IVmFactory
|
||||||
|
{
|
||||||
|
PropertyViewModel PropertyViewModel(ILayerProperty layerProperty);
|
||||||
|
PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup);
|
||||||
|
PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerBrush layerBrush);
|
||||||
|
PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerEffect layerEffect);
|
||||||
|
|
||||||
|
TreeGroupViewModel TreeGroupViewModel(PropertyGroupViewModel propertyGroupViewModel);
|
||||||
|
|
||||||
|
TimelineViewModel TimelineViewModel(ObservableCollection<PropertyGroupViewModel> propertyGroupViewModels);
|
||||||
|
TimelineGroupViewModel TimelineGroupViewModel(PropertyGroupViewModel propertyGroupViewModel);
|
||||||
|
}
|
||||||
|
public class LayerPropertyVmFactory : ILayerPropertyVmFactory
|
||||||
|
{
|
||||||
|
private readonly IContainer _container;
|
||||||
|
|
||||||
|
public LayerPropertyVmFactory(IContainer container)
|
||||||
|
{
|
||||||
|
_container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PropertyViewModel PropertyViewModel(ILayerProperty layerProperty)
|
||||||
|
{
|
||||||
|
return _container.Resolve<PropertyViewModel>(new object[] { layerProperty });
|
||||||
|
}
|
||||||
|
|
||||||
|
public PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup)
|
||||||
|
{
|
||||||
|
return _container.Resolve<PropertyGroupViewModel>(new object[] { layerPropertyGroup });
|
||||||
|
}
|
||||||
|
|
||||||
|
public PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerBrush layerBrush)
|
||||||
|
{
|
||||||
|
return _container.Resolve<PropertyGroupViewModel>(new object[] { layerPropertyGroup, layerBrush });
|
||||||
|
}
|
||||||
|
|
||||||
|
public PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerEffect layerEffect)
|
||||||
|
{
|
||||||
|
return _container.Resolve<PropertyGroupViewModel>(new object[] { layerPropertyGroup, layerEffect });
|
||||||
|
}
|
||||||
|
|
||||||
|
public TreeGroupViewModel TreeGroupViewModel(PropertyGroupViewModel propertyGroupViewModel)
|
||||||
|
{
|
||||||
|
return _container.Resolve<TreeGroupViewModel>(new object[] { propertyGroupViewModel });
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimelineViewModel TimelineViewModel(ObservableCollection<PropertyGroupViewModel> propertyGroupViewModels)
|
||||||
|
{
|
||||||
|
return _container.Resolve<TimelineViewModel>(new object[] { propertyGroupViewModels });
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimelineGroupViewModel TimelineGroupViewModel(PropertyGroupViewModel propertyGroupViewModel)
|
||||||
|
{
|
||||||
|
return _container.Resolve<TimelineGroupViewModel>(new object[] { propertyGroupViewModel });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IDataBindingVmFactory : IVmFactory
|
||||||
|
{
|
||||||
|
DataBindingViewModel DataBindingViewModel();
|
||||||
|
}
|
||||||
|
public class DataBindingVmFactory : IDataBindingVmFactory
|
||||||
|
{
|
||||||
|
private readonly IContainer _container;
|
||||||
|
|
||||||
|
public DataBindingVmFactory(IContainer container)
|
||||||
|
{
|
||||||
|
_container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataBindingViewModel DataBindingViewModel()
|
||||||
|
{
|
||||||
|
return _container.Resolve<DataBindingViewModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IPropertyVmFactory
|
||||||
|
{
|
||||||
|
ITreePropertyViewModel TreePropertyViewModel(ILayerProperty layerProperty, PropertyViewModel propertyViewModel);
|
||||||
|
ITimelinePropertyViewModel TimelinePropertyViewModel(ILayerProperty layerProperty, PropertyViewModel propertyViewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface INodeVmFactory : IVmFactory
|
||||||
|
{
|
||||||
|
NodeScriptViewModel NodeScriptViewModel(NodeScript nodeScript, bool isPreview);
|
||||||
|
NodePickerViewModel NodePickerViewModel(NodeScript nodeScript);
|
||||||
|
NodeViewModel NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node);
|
||||||
|
CableViewModel CableViewModel(NodeScriptViewModel nodeScriptViewModel, IPin from, IPin to);
|
||||||
|
DragCableViewModel DragCableViewModel(PinViewModel pinViewModel);
|
||||||
|
InputPinViewModel InputPinViewModel(IPin inputPin, NodeScriptViewModel nodeScriptViewModel);
|
||||||
|
OutputPinViewModel OutputPinViewModel(IPin outputPin, NodeScriptViewModel nodeScriptViewModel);
|
||||||
|
InputPinCollectionViewModel InputPinCollectionViewModel(IPinCollection inputPinCollection, NodeScriptViewModel nodeScriptViewModel);
|
||||||
|
OutputPinCollectionViewModel OutputPinCollectionViewModel(IPinCollection outputPinCollection, NodeScriptViewModel nodeScriptViewModel);
|
||||||
|
}
|
||||||
|
public class NodeVmFactory : INodeVmFactory
|
||||||
|
{
|
||||||
|
private readonly IContainer _container;
|
||||||
|
|
||||||
|
public NodeVmFactory(IContainer container)
|
||||||
|
{
|
||||||
|
_container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodeScriptViewModel NodeScriptViewModel(NodeScript nodeScript, bool isPreview)
|
||||||
|
{
|
||||||
|
return _container.Resolve<NodeScriptViewModel>(new object[] { nodeScript, isPreview });
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodePickerViewModel NodePickerViewModel(NodeScript nodeScript)
|
||||||
|
{
|
||||||
|
return _container.Resolve<NodePickerViewModel>(new object[] { nodeScript });
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodeViewModel NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node)
|
||||||
|
{
|
||||||
|
return _container.Resolve<NodeViewModel>(new object[] { nodeScriptViewModel, node });
|
||||||
|
}
|
||||||
|
|
||||||
|
public CableViewModel CableViewModel(NodeScriptViewModel nodeScriptViewModel, IPin from, IPin to)
|
||||||
|
{
|
||||||
|
return _container.Resolve<CableViewModel>(new object[] { nodeScriptViewModel, from, to });
|
||||||
|
}
|
||||||
|
|
||||||
|
public DragCableViewModel DragCableViewModel(PinViewModel pinViewModel)
|
||||||
|
{
|
||||||
|
return _container.Resolve<DragCableViewModel>(new object[] { pinViewModel });
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputPinViewModel InputPinViewModel(IPin inputPin, NodeScriptViewModel nodeScriptViewModel)
|
||||||
|
{
|
||||||
|
return _container.Resolve<InputPinViewModel>(new object[] { inputPin, nodeScriptViewModel });
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputPinViewModel OutputPinViewModel(IPin outputPin, NodeScriptViewModel nodeScriptViewModel)
|
||||||
|
{
|
||||||
|
return _container.Resolve<OutputPinViewModel>(new object[] { outputPin, nodeScriptViewModel });
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputPinCollectionViewModel InputPinCollectionViewModel(IPinCollection inputPinCollection, NodeScriptViewModel nodeScriptViewModel)
|
||||||
|
{
|
||||||
|
return _container.Resolve<InputPinCollectionViewModel>(new object[] { inputPinCollection, nodeScriptViewModel });
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputPinCollectionViewModel OutputPinCollectionViewModel(IPinCollection outputPinCollection, NodeScriptViewModel nodeScriptViewModel)
|
||||||
|
{
|
||||||
|
return _container.Resolve<OutputPinCollectionViewModel>(new object[] { outputPinCollection, nodeScriptViewModel });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IConditionVmFactory : IVmFactory
|
||||||
|
{
|
||||||
|
AlwaysOnConditionViewModel AlwaysOnConditionViewModel(AlwaysOnCondition alwaysOnCondition);
|
||||||
|
PlayOnceConditionViewModel PlayOnceConditionViewModel(PlayOnceCondition playOnceCondition);
|
||||||
|
StaticConditionViewModel StaticConditionViewModel(StaticCondition staticCondition);
|
||||||
|
EventConditionViewModel EventConditionViewModel(EventCondition eventCondition);
|
||||||
|
}
|
||||||
|
public class ConditionVmFactory : IConditionVmFactory
|
||||||
|
{
|
||||||
|
private readonly IContainer _container;
|
||||||
|
|
||||||
|
public ConditionVmFactory(IContainer container)
|
||||||
|
{
|
||||||
|
_container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlwaysOnConditionViewModel AlwaysOnConditionViewModel(AlwaysOnCondition alwaysOnCondition)
|
||||||
|
{
|
||||||
|
return _container.Resolve<AlwaysOnConditionViewModel>(new object[] { alwaysOnCondition });
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayOnceConditionViewModel PlayOnceConditionViewModel(PlayOnceCondition playOnceCondition)
|
||||||
|
{
|
||||||
|
return _container.Resolve<PlayOnceConditionViewModel>(new object[] { playOnceCondition });
|
||||||
|
}
|
||||||
|
|
||||||
|
public StaticConditionViewModel StaticConditionViewModel(StaticCondition staticCondition)
|
||||||
|
{
|
||||||
|
return _container.Resolve<StaticConditionViewModel>(new object[] { staticCondition });
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventConditionViewModel EventConditionViewModel(EventCondition eventCondition)
|
||||||
|
{
|
||||||
|
return _container.Resolve<EventConditionViewModel>(new object[] { eventCondition });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ILayerHintVmFactory : IVmFactory
|
||||||
|
{
|
||||||
|
CategoryAdaptionHintViewModel CategoryAdaptionHintViewModel(Layer layer, CategoryAdaptionHint adaptionHint);
|
||||||
|
DeviceAdaptionHintViewModel DeviceAdaptionHintViewModel(Layer layer, DeviceAdaptionHint adaptionHint);
|
||||||
|
KeyboardSectionAdaptionHintViewModel KeyboardSectionAdaptionHintViewModel(Layer layer, KeyboardSectionAdaptionHint adaptionHint);
|
||||||
|
}
|
||||||
|
public class LayerHintVmFactory : ILayerHintVmFactory
|
||||||
|
{
|
||||||
|
private readonly IContainer _container;
|
||||||
|
|
||||||
|
public LayerHintVmFactory(IContainer container)
|
||||||
|
{
|
||||||
|
_container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CategoryAdaptionHintViewModel CategoryAdaptionHintViewModel(Layer layer, CategoryAdaptionHint adaptionHint)
|
||||||
|
{
|
||||||
|
return _container.Resolve<CategoryAdaptionHintViewModel>(new object[] { layer, adaptionHint });
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeviceAdaptionHintViewModel DeviceAdaptionHintViewModel(Layer layer, DeviceAdaptionHint adaptionHint)
|
||||||
|
{
|
||||||
|
return _container.Resolve<DeviceAdaptionHintViewModel>(new object[] { layer, adaptionHint });
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyboardSectionAdaptionHintViewModel KeyboardSectionAdaptionHintViewModel(Layer layer, KeyboardSectionAdaptionHint adaptionHint)
|
||||||
|
{
|
||||||
|
return _container.Resolve<KeyboardSectionAdaptionHintViewModel>(new object[] { layer, adaptionHint });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IScriptVmFactory : IVmFactory
|
||||||
|
{
|
||||||
|
ScriptConfigurationViewModel ScriptConfigurationViewModel(ScriptConfiguration scriptConfiguration);
|
||||||
|
ScriptConfigurationViewModel ScriptConfigurationViewModel(Profile profile, ScriptConfiguration scriptConfiguration);
|
||||||
|
}
|
||||||
|
public class ScriptVmFactory : IScriptVmFactory
|
||||||
|
{
|
||||||
|
private readonly IContainer _container;
|
||||||
|
|
||||||
|
public ScriptVmFactory(IContainer container)
|
||||||
|
{
|
||||||
|
_container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScriptConfigurationViewModel ScriptConfigurationViewModel(ScriptConfiguration scriptConfiguration)
|
||||||
|
{
|
||||||
|
return _container.Resolve<ScriptConfigurationViewModel>(new object[] { scriptConfiguration });
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScriptConfigurationViewModel ScriptConfigurationViewModel(Profile profile, ScriptConfiguration scriptConfiguration)
|
||||||
|
{
|
||||||
|
return _container.Resolve<ScriptConfigurationViewModel>(new object[] { profile, scriptConfiguration });
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
using System;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.DryIoc.Factories;
|
||||||
|
using Artemis.UI.Exceptions;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor.Properties;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||||
|
using DryIoc;
|
||||||
|
|
||||||
|
namespace Artemis.UI.DryIoc.InstanceProviders;
|
||||||
|
|
||||||
|
public class PropertyVmFactory : IPropertyVmFactory
|
||||||
|
{
|
||||||
|
private readonly IContainer _container;
|
||||||
|
|
||||||
|
public PropertyVmFactory(IContainer container)
|
||||||
|
{
|
||||||
|
_container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITimelinePropertyViewModel TimelinePropertyViewModel(ILayerProperty layerProperty, PropertyViewModel propertyViewModel)
|
||||||
|
{
|
||||||
|
// Find LayerProperty type
|
||||||
|
Type? layerPropertyType = layerProperty.GetType();
|
||||||
|
while (layerPropertyType != null && (!layerPropertyType.IsGenericType || layerPropertyType.GetGenericTypeDefinition() != typeof(LayerProperty<>)))
|
||||||
|
layerPropertyType = layerPropertyType.BaseType;
|
||||||
|
if (layerPropertyType == null)
|
||||||
|
throw new ArtemisUIException("Could not find the LayerProperty type");
|
||||||
|
|
||||||
|
Type? genericType = typeof(TimelinePropertyViewModel<>).MakeGenericType(layerPropertyType.GetGenericArguments());
|
||||||
|
return (ITimelinePropertyViewModel)_container.Resolve(genericType, new object[] { layerProperty, propertyViewModel });
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITreePropertyViewModel TreePropertyViewModel(ILayerProperty layerProperty, PropertyViewModel propertyViewModel)
|
||||||
|
{
|
||||||
|
// Find LayerProperty type
|
||||||
|
Type? layerPropertyType = layerProperty.GetType();
|
||||||
|
while (layerPropertyType != null && (!layerPropertyType.IsGenericType || layerPropertyType.GetGenericTypeDefinition() != typeof(LayerProperty<>)))
|
||||||
|
layerPropertyType = layerPropertyType.BaseType;
|
||||||
|
if (layerPropertyType == null)
|
||||||
|
throw new ArtemisUIException("Could not find the LayerProperty type");
|
||||||
|
|
||||||
|
Type? genericType = typeof(TreePropertyViewModel<>).MakeGenericType(layerPropertyType.GetGenericArguments());
|
||||||
|
|
||||||
|
return (ITreePropertyViewModel)_container.Resolve(genericType, new object[] { layerProperty, propertyViewModel });
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/Artemis.UI/Extensions/CompositeDisposableExtensions.cs
Normal file
14
src/Artemis.UI/Extensions/CompositeDisposableExtensions.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using System.Reactive.Disposables;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Extensions;
|
||||||
|
|
||||||
|
public static class CompositeDisposableExtensions
|
||||||
|
{
|
||||||
|
public static CancellationToken AsCancellationToken(this CompositeDisposable disposable)
|
||||||
|
{
|
||||||
|
CancellationTokenSource tokenSource = new();
|
||||||
|
Disposable.Create(tokenSource, s => s.Cancel()).DisposeWith(disposable);
|
||||||
|
return tokenSource.Token;
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/Artemis.UI/Extensions/HttpClientExtensions.cs
Normal file
56
src/Artemis.UI/Extensions/HttpClientExtensions.cs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Extensions
|
||||||
|
{
|
||||||
|
public static class HttpClientProgressExtensions
|
||||||
|
{
|
||||||
|
public static async Task DownloadDataAsync(this HttpClient client, string requestUrl, Stream destination, IProgress<float>? progress, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
using HttpResponseMessage response = await client.GetAsync(requestUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
long? contentLength = response.Content.Headers.ContentLength;
|
||||||
|
await using Stream download = await response.Content.ReadAsStreamAsync(cancellationToken);
|
||||||
|
// no progress... no contentLength... very sad
|
||||||
|
if (progress is null || !contentLength.HasValue)
|
||||||
|
{
|
||||||
|
await download.CopyToAsync(destination, cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Such progress and contentLength much reporting Wow!
|
||||||
|
Progress<long> progressWrapper = new(totalBytes => progress.Report(GetProgressPercentage(totalBytes, contentLength.Value)));
|
||||||
|
await download.CopyToAsync(destination, 81920, progressWrapper, cancellationToken);
|
||||||
|
|
||||||
|
float GetProgressPercentage(float totalBytes, float currentBytes) => (totalBytes / currentBytes) * 100f;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress<long> progress, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (bufferSize < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(bufferSize));
|
||||||
|
if (source is null)
|
||||||
|
throw new ArgumentNullException(nameof(source));
|
||||||
|
if (!source.CanRead)
|
||||||
|
throw new InvalidOperationException($"'{nameof(source)}' is not readable.");
|
||||||
|
if (destination == null)
|
||||||
|
throw new ArgumentNullException(nameof(destination));
|
||||||
|
if (!destination.CanWrite)
|
||||||
|
throw new InvalidOperationException($"'{nameof(destination)}' is not writable.");
|
||||||
|
|
||||||
|
byte[] buffer = new byte[bufferSize];
|
||||||
|
long totalBytesRead = 0;
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
||||||
|
{
|
||||||
|
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
|
||||||
|
totalBytesRead += bytesRead;
|
||||||
|
progress?.Report(totalBytesRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/Artemis.UI/Extensions/ZipArchiveExtensions.cs
Normal file
74
src/Artemis.UI/Extensions/ZipArchiveExtensions.cs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Extensions;
|
||||||
|
|
||||||
|
// Taken from System.IO.Compression with progress reporting slapped on top
|
||||||
|
public static class ZipArchiveExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts all the files in the zip archive to a directory on the file system.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The zip archive to extract files from.</param>
|
||||||
|
/// <param name="destinationDirectoryName">The path to the directory to place the extracted files in. You can specify either a relative or an absolute path. A relative path is interpreted as relative to the current working directory.</param>
|
||||||
|
/// <param name="overwriteFiles">A boolean indicating whether to override existing files</param>
|
||||||
|
/// <param name="progress">The progress to report to.</param>
|
||||||
|
/// <param name="cancellationToken">A cancellation token</param>
|
||||||
|
public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName, bool overwriteFiles, IProgress<float> progress, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (source == null)
|
||||||
|
throw new ArgumentNullException(nameof(source));
|
||||||
|
|
||||||
|
if (destinationDirectoryName == null)
|
||||||
|
throw new ArgumentNullException(nameof(destinationDirectoryName));
|
||||||
|
|
||||||
|
for (int index = 0; index < source.Entries.Count; index++)
|
||||||
|
{
|
||||||
|
ZipArchiveEntry entry = source.Entries[index];
|
||||||
|
entry.ExtractRelativeToDirectory(destinationDirectoryName, overwriteFiles);
|
||||||
|
progress.Report((index + 1f) / source.Entries.Count * 100f);
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ExtractRelativeToDirectory(this ZipArchiveEntry source, string destinationDirectoryName, bool overwrite)
|
||||||
|
{
|
||||||
|
if (source == null)
|
||||||
|
throw new ArgumentNullException(nameof(source));
|
||||||
|
|
||||||
|
if (destinationDirectoryName == null)
|
||||||
|
throw new ArgumentNullException(nameof(destinationDirectoryName));
|
||||||
|
|
||||||
|
// Note that this will give us a good DirectoryInfo even if destinationDirectoryName exists:
|
||||||
|
DirectoryInfo di = Directory.CreateDirectory(destinationDirectoryName);
|
||||||
|
string destinationDirectoryFullPath = di.FullName;
|
||||||
|
if (!destinationDirectoryFullPath.EndsWith(Path.DirectorySeparatorChar))
|
||||||
|
destinationDirectoryFullPath += Path.DirectorySeparatorChar;
|
||||||
|
|
||||||
|
string fileDestinationPath = Path.GetFullPath(Path.Combine(destinationDirectoryFullPath, source.FullName));
|
||||||
|
|
||||||
|
if (!fileDestinationPath.StartsWith(destinationDirectoryFullPath, StringComparison))
|
||||||
|
throw new IOException($"The file '{fileDestinationPath}' already exists.");
|
||||||
|
|
||||||
|
if (Path.GetFileName(fileDestinationPath).Length == 0)
|
||||||
|
{
|
||||||
|
// If it is a directory:
|
||||||
|
|
||||||
|
if (source.Length != 0)
|
||||||
|
throw new IOException("Extracting Zip entry would have resulted in a file outside the specified destination directory.");
|
||||||
|
|
||||||
|
Directory.CreateDirectory(fileDestinationPath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If it is a file:
|
||||||
|
// Create containing directory:
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(fileDestinationPath)!);
|
||||||
|
source.ExtractToFile(fileDestinationPath, overwrite: overwrite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static StringComparison StringComparison => IsCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
|
||||||
|
private static bool IsCaseSensitive => !(OperatingSystem.IsWindows() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS() || OperatingSystem.IsTvOS() || OperatingSystem.IsWatchOS());
|
||||||
|
}
|
||||||
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Storage.Entities.Profile.Nodes;
|
using Artemis.Storage.Entities.Profile.Nodes;
|
||||||
using FluentAvalonia.Core;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Models;
|
namespace Artemis.UI.Models;
|
||||||
|
|
||||||
|
|||||||
@ -1,133 +0,0 @@
|
|||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Reactive;
|
|
||||||
using Artemis.Core;
|
|
||||||
using Artemis.Core.LayerBrushes;
|
|
||||||
using Artemis.Core.LayerEffects;
|
|
||||||
using Artemis.Core.ScriptingProviders;
|
|
||||||
using Artemis.UI.Screens.Device;
|
|
||||||
using Artemis.UI.Screens.Plugins;
|
|
||||||
using Artemis.UI.Screens.ProfileEditor;
|
|
||||||
using Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes;
|
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileTree;
|
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints;
|
|
||||||
using Artemis.UI.Screens.ProfileEditor.Properties;
|
|
||||||
using Artemis.UI.Screens.ProfileEditor.Properties.DataBinding;
|
|
||||||
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
|
||||||
using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
|
||||||
using Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
|
|
||||||
using Artemis.UI.Screens.Scripting;
|
|
||||||
using Artemis.UI.Screens.Settings;
|
|
||||||
using Artemis.UI.Screens.Sidebar;
|
|
||||||
using Artemis.UI.Screens.SurfaceEditor;
|
|
||||||
using Artemis.UI.Screens.VisualScripting;
|
|
||||||
using Artemis.UI.Screens.VisualScripting.Pins;
|
|
||||||
using ReactiveUI;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Ninject.Factories;
|
|
||||||
|
|
||||||
public interface IVmFactory
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IDeviceVmFactory : IVmFactory
|
|
||||||
{
|
|
||||||
DevicePropertiesViewModel DevicePropertiesViewModel(ArtemisDevice device);
|
|
||||||
DeviceSettingsViewModel DeviceSettingsViewModel(ArtemisDevice device, DevicesTabViewModel devicesTabViewModel);
|
|
||||||
DeviceDetectInputViewModel DeviceDetectInputViewModel(ArtemisDevice device);
|
|
||||||
DevicePropertiesTabViewModel DevicePropertiesTabViewModel(ArtemisDevice device);
|
|
||||||
DeviceInfoTabViewModel DeviceInfoTabViewModel(ArtemisDevice device);
|
|
||||||
DeviceLedsTabViewModel DeviceLedsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds);
|
|
||||||
InputMappingsTabViewModel InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ISettingsVmFactory : IVmFactory
|
|
||||||
{
|
|
||||||
PluginSettingsViewModel PluginSettingsViewModel(Plugin plugin);
|
|
||||||
PluginViewModel PluginViewModel(Plugin plugin, ReactiveCommand<Unit, Unit>? reload);
|
|
||||||
PluginFeatureViewModel PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, bool showShield);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ISidebarVmFactory : IVmFactory
|
|
||||||
{
|
|
||||||
SidebarViewModel? SidebarViewModel(IScreen hostScreen);
|
|
||||||
SidebarCategoryViewModel SidebarCategoryViewModel(ProfileCategory profileCategory);
|
|
||||||
SidebarProfileConfigurationViewModel SidebarProfileConfigurationViewModel(ProfileConfiguration profileConfiguration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ISurfaceVmFactory : IVmFactory
|
|
||||||
{
|
|
||||||
SurfaceDeviceViewModel SurfaceDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel);
|
|
||||||
ListDeviceViewModel ListDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IPrerequisitesVmFactory : IVmFactory
|
|
||||||
{
|
|
||||||
PluginPrerequisiteViewModel PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IProfileEditorVmFactory : IVmFactory
|
|
||||||
{
|
|
||||||
ProfileEditorViewModel ProfileEditorViewModel(IScreen hostScreen);
|
|
||||||
FolderTreeItemViewModel FolderTreeItemViewModel(TreeItemViewModel? parent, Folder folder);
|
|
||||||
LayerTreeItemViewModel LayerTreeItemViewModel(TreeItemViewModel? parent, Layer layer);
|
|
||||||
LayerShapeVisualizerViewModel LayerShapeVisualizerViewModel(Layer layer);
|
|
||||||
LayerVisualizerViewModel LayerVisualizerViewModel(Layer layer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ILayerPropertyVmFactory : IVmFactory
|
|
||||||
{
|
|
||||||
PropertyViewModel PropertyViewModel(ILayerProperty layerProperty);
|
|
||||||
PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup);
|
|
||||||
PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerBrush layerBrush);
|
|
||||||
PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerEffect layerEffect);
|
|
||||||
|
|
||||||
TreeGroupViewModel TreeGroupViewModel(PropertyGroupViewModel propertyGroupViewModel);
|
|
||||||
|
|
||||||
TimelineViewModel TimelineViewModel(ObservableCollection<PropertyGroupViewModel> propertyGroupViewModels);
|
|
||||||
TimelineGroupViewModel TimelineGroupViewModel(PropertyGroupViewModel propertyGroupViewModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IDataBindingVmFactory : IVmFactory
|
|
||||||
{
|
|
||||||
DataBindingViewModel DataBindingViewModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IPropertyVmFactory
|
|
||||||
{
|
|
||||||
ITreePropertyViewModel TreePropertyViewModel(ILayerProperty layerProperty, PropertyViewModel propertyViewModel);
|
|
||||||
ITimelinePropertyViewModel TimelinePropertyViewModel(ILayerProperty layerProperty, PropertyViewModel propertyViewModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface INodeVmFactory : IVmFactory
|
|
||||||
{
|
|
||||||
NodeScriptViewModel NodeScriptViewModel(NodeScript nodeScript, bool isPreview);
|
|
||||||
NodePickerViewModel NodePickerViewModel(NodeScript nodeScript);
|
|
||||||
NodeViewModel NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node);
|
|
||||||
CableViewModel CableViewModel(NodeScriptViewModel nodeScriptViewModel, IPin from, IPin to);
|
|
||||||
DragCableViewModel DragCableViewModel(PinViewModel pinViewModel);
|
|
||||||
InputPinViewModel InputPinViewModel(IPin inputPin, NodeScriptViewModel nodeScriptViewModel);
|
|
||||||
OutputPinViewModel OutputPinViewModel(IPin outputPin, NodeScriptViewModel nodeScriptViewModel);
|
|
||||||
InputPinCollectionViewModel InputPinCollectionViewModel(IPinCollection inputPinCollection, NodeScriptViewModel nodeScriptViewModel);
|
|
||||||
OutputPinCollectionViewModel OutputPinCollectionViewModel(IPinCollection outputPinCollection, NodeScriptViewModel nodeScriptViewModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IConditionVmFactory : IVmFactory
|
|
||||||
{
|
|
||||||
AlwaysOnConditionViewModel AlwaysOnConditionViewModel(AlwaysOnCondition alwaysOnCondition);
|
|
||||||
PlayOnceConditionViewModel PlayOnceConditionViewModel(PlayOnceCondition playOnceCondition);
|
|
||||||
StaticConditionViewModel StaticConditionViewModel(StaticCondition staticCondition);
|
|
||||||
EventConditionViewModel EventConditionViewModel(EventCondition eventCondition);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ILayerHintVmFactory : IVmFactory
|
|
||||||
{
|
|
||||||
CategoryAdaptionHintViewModel CategoryAdaptionHintViewModel(Layer layer, CategoryAdaptionHint adaptionHint);
|
|
||||||
DeviceAdaptionHintViewModel DeviceAdaptionHintViewModel(Layer layer, DeviceAdaptionHint adaptionHint);
|
|
||||||
KeyboardSectionAdaptionHintViewModel KeyboardSectionAdaptionHintViewModel(Layer layer, KeyboardSectionAdaptionHint adaptionHint);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IScriptVmFactory : IVmFactory
|
|
||||||
{
|
|
||||||
ScriptConfigurationViewModel ScriptConfigurationViewModel(ScriptConfiguration scriptConfiguration);
|
|
||||||
ScriptConfigurationViewModel ScriptConfigurationViewModel(Profile profile, ScriptConfiguration scriptConfiguration);
|
|
||||||
}
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Reflection;
|
|
||||||
using Artemis.Core;
|
|
||||||
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
|
||||||
using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
|
||||||
using Ninject.Extensions.Factory;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Ninject.InstanceProviders;
|
|
||||||
|
|
||||||
public class LayerPropertyViewModelInstanceProvider : StandardInstanceProvider
|
|
||||||
{
|
|
||||||
protected override Type GetType(MethodInfo methodInfo, object[] arguments)
|
|
||||||
{
|
|
||||||
if (methodInfo.ReturnType != typeof(ITreePropertyViewModel) && methodInfo.ReturnType != typeof(ITimelinePropertyViewModel))
|
|
||||||
return base.GetType(methodInfo, arguments);
|
|
||||||
|
|
||||||
// Find LayerProperty type
|
|
||||||
Type? layerPropertyType = arguments[0].GetType();
|
|
||||||
while (layerPropertyType != null && (!layerPropertyType.IsGenericType || layerPropertyType.GetGenericTypeDefinition() != typeof(LayerProperty<>)))
|
|
||||||
layerPropertyType = layerPropertyType.BaseType;
|
|
||||||
if (layerPropertyType == null)
|
|
||||||
return base.GetType(methodInfo, arguments);
|
|
||||||
|
|
||||||
if (methodInfo.ReturnType == typeof(ITreePropertyViewModel))
|
|
||||||
return typeof(TreePropertyViewModel<>).MakeGenericType(layerPropertyType.GetGenericArguments());
|
|
||||||
if (methodInfo.ReturnType == typeof(ITimelinePropertyViewModel))
|
|
||||||
return typeof(TimelinePropertyViewModel<>).MakeGenericType(layerPropertyType.GetGenericArguments());
|
|
||||||
|
|
||||||
return base.GetType(methodInfo, arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Artemis.UI.Ninject.Factories;
|
|
||||||
using Artemis.UI.Ninject.InstanceProviders;
|
|
||||||
using Artemis.UI.Screens;
|
|
||||||
using Artemis.UI.Screens.VisualScripting;
|
|
||||||
using Artemis.UI.Services.Interfaces;
|
|
||||||
using Artemis.UI.Shared;
|
|
||||||
using Artemis.UI.Shared.Services.NodeEditor;
|
|
||||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
|
||||||
using Avalonia.Platform;
|
|
||||||
using Avalonia.Shared.PlatformSupport;
|
|
||||||
using Ninject.Extensions.Conventions;
|
|
||||||
using Ninject.Extensions.Factory;
|
|
||||||
using Ninject.Modules;
|
|
||||||
using Ninject.Planning.Bindings.Resolvers;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Ninject;
|
|
||||||
|
|
||||||
public class UIModule : NinjectModule
|
|
||||||
{
|
|
||||||
public override void Load()
|
|
||||||
{
|
|
||||||
if (Kernel == null)
|
|
||||||
throw new ArgumentNullException("Kernel shouldn't be null here.");
|
|
||||||
|
|
||||||
Kernel.Components.Add<IMissingBindingResolver, SelfBindingResolver>();
|
|
||||||
Kernel.Bind<IAssetLoader>().ToConstant(new AssetLoader());
|
|
||||||
|
|
||||||
Kernel.Bind(x =>
|
|
||||||
{
|
|
||||||
x.FromThisAssembly()
|
|
||||||
.SelectAllClasses()
|
|
||||||
.InheritedFrom<ViewModelBase>()
|
|
||||||
.BindToSelf();
|
|
||||||
});
|
|
||||||
|
|
||||||
Kernel.Bind(x =>
|
|
||||||
{
|
|
||||||
x.FromThisAssembly()
|
|
||||||
.SelectAllClasses()
|
|
||||||
.InheritedFrom<MainScreenViewModel>()
|
|
||||||
.BindAllBaseClasses();
|
|
||||||
});
|
|
||||||
|
|
||||||
Kernel.Bind(x =>
|
|
||||||
{
|
|
||||||
x.FromThisAssembly()
|
|
||||||
.SelectAllClasses()
|
|
||||||
.InheritedFrom<IToolViewModel>()
|
|
||||||
.BindAllInterfaces();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Bind UI factories
|
|
||||||
Kernel.Bind(x =>
|
|
||||||
{
|
|
||||||
x.FromThisAssembly()
|
|
||||||
.SelectAllInterfaces()
|
|
||||||
.InheritedFrom<IVmFactory>()
|
|
||||||
.BindToFactory();
|
|
||||||
});
|
|
||||||
|
|
||||||
Kernel.Bind<NodeScriptWindowViewModelBase>().To<NodeScriptWindowViewModel>();
|
|
||||||
Kernel.Bind<IPropertyVmFactory>().ToFactory(() => new LayerPropertyViewModelInstanceProvider());
|
|
||||||
|
|
||||||
// Bind all UI services as singletons
|
|
||||||
Kernel.Bind(x =>
|
|
||||||
{
|
|
||||||
x.FromThisAssembly()
|
|
||||||
.SelectAllClasses()
|
|
||||||
.InheritedFrom<IArtemisUIService>()
|
|
||||||
.BindAllInterfaces()
|
|
||||||
.Configure(c => c.InSingletonScope());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -15,6 +15,7 @@
|
|||||||
Height="800">
|
Height="800">
|
||||||
|
|
||||||
<Window.Styles>
|
<Window.Styles>
|
||||||
|
<StyleInclude Source="avares://AvaloniaEdit/AvaloniaEdit.xaml" />
|
||||||
<Style Selector="StackPanel.sidebar-stackpanel avalonia|MaterialIcon">
|
<Style Selector="StackPanel.sidebar-stackpanel avalonia|MaterialIcon">
|
||||||
<Setter Property="Margin" Value="-7 0 0 0" />
|
<Setter Property="Margin" Value="-7 0 0 0" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|||||||
@ -2,15 +2,25 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:aedit="https://github.com/avaloniaui/avaloniaedit"
|
||||||
|
xmlns:controls="clr-namespace:Artemis.UI.Controls"
|
||||||
|
xmlns:logs="clr-namespace:Artemis.UI.Screens.Debugger.Logs"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.Debugger.Logs.LogsDebugView">
|
x:Class="Artemis.UI.Screens.Debugger.Logs.LogsDebugView"
|
||||||
<StackPanel>
|
x:DataType="logs:LogsDebugViewModel">
|
||||||
<TextBlock Classes="h3">Logs</TextBlock>
|
<aedit:TextEditor Name="log"
|
||||||
<TextBlock TextWrapping="Wrap">
|
Document="{ CompiledBinding Document }"
|
||||||
On this page you can view Artemis's logs in real-time. Logging can come from Artemis itself, plugins and scripts.
|
IsReadOnly="True"
|
||||||
</TextBlock>
|
FontFamily="Consolas"
|
||||||
|
FontSize="12"
|
||||||
|
HorizontalScrollBarVisibility="Auto"
|
||||||
|
Padding="0 15 15 15"
|
||||||
|
TextChanged="OnTextChanged" >
|
||||||
|
|
||||||
<TextBlock Margin="0 20 0 0">TODO as there's no FlowDocumentScrollViewer in Avalonia and I'm too lazy to come up with an alternative.</TextBlock>
|
<aedit:TextEditor.Styles>
|
||||||
<TextBlock Classes="subtitle">#feelsbadman</TextBlock>
|
<Style Selector="aedit|TextArea">
|
||||||
</StackPanel>
|
<Setter Property="SelectionBrush" Value="#44ffffff" />
|
||||||
|
</Style>
|
||||||
|
</aedit:TextEditor.Styles>
|
||||||
|
</aedit:TextEditor>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -1,17 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using AvaloniaEdit;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Debugger.Logs;
|
namespace Artemis.UI.Screens.Debugger.Logs;
|
||||||
|
|
||||||
public class LogsDebugView : ReactiveUserControl<LogsDebugViewModel>
|
public class LogsDebugView : ReactiveUserControl<LogsDebugViewModel>
|
||||||
{
|
{
|
||||||
|
private int _lineCount;
|
||||||
|
private TextEditor _textEditor;
|
||||||
|
|
||||||
public LogsDebugView()
|
public LogsDebugView()
|
||||||
{
|
{
|
||||||
|
_lineCount = 0;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
_textEditor = this.FindControl<TextEditor>("log");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
base.OnInitialized();
|
||||||
|
Dispatcher.UIThread.Post(() => _textEditor.ScrollToEnd(), DispatcherPriority.ApplicationIdle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTextChanged(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (_textEditor is null)
|
||||||
|
return;
|
||||||
|
if (_textEditor.ExtentHeight == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int linesAdded = _textEditor.LineCount - _lineCount;
|
||||||
|
double lineHeight = _textEditor.ExtentHeight / _textEditor.LineCount;
|
||||||
|
double outOfScreenTextHeight = _textEditor.ExtentHeight - _textEditor.VerticalOffset - _textEditor.ViewportHeight;
|
||||||
|
double outOfScreenLines = outOfScreenTextHeight / lineHeight;
|
||||||
|
|
||||||
|
//we need this help distance because of rounding.
|
||||||
|
//if we scroll slightly above the end, we still want it
|
||||||
|
//to scroll down to the new lines.
|
||||||
|
const double graceDistance = 1d;
|
||||||
|
|
||||||
|
//if we were at the bottom of the log and
|
||||||
|
//if the last log event was 5 lines long
|
||||||
|
//we will be 5 lines out sync.
|
||||||
|
//if this is the case, scroll down.
|
||||||
|
|
||||||
|
//if we are more than that out of sync,
|
||||||
|
//the user scrolled up and we should not
|
||||||
|
//mess with anything.
|
||||||
|
if (_lineCount == 0 || linesAdded + graceDistance > outOfScreenLines)
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() => _textEditor.ScrollToEnd(), DispatcherPriority.ApplicationIdle);
|
||||||
|
_lineCount = _textEditor.LineCount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,11 +1,74 @@
|
|||||||
using Artemis.UI.Shared;
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using AvaloniaEdit.Document;
|
||||||
|
using ReactiveUI;
|
||||||
|
using Serilog.Events;
|
||||||
|
using Serilog.Formatting.Display;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Debugger.Logs;
|
namespace Artemis.UI.Screens.Debugger.Logs;
|
||||||
|
|
||||||
public class LogsDebugViewModel : ActivatableViewModelBase
|
public class LogsDebugViewModel : ActivatableViewModelBase
|
||||||
{
|
{
|
||||||
|
private readonly MessageTemplateTextFormatter _formatter;
|
||||||
|
|
||||||
|
public TextDocument Document { get; }
|
||||||
|
|
||||||
|
private const int MAX_ENTRIES = 1000;
|
||||||
|
|
||||||
public LogsDebugViewModel()
|
public LogsDebugViewModel()
|
||||||
{
|
{
|
||||||
DisplayName = "Logs";
|
DisplayName = "Logs";
|
||||||
|
Document = new TextDocument();
|
||||||
|
_formatter = new MessageTemplateTextFormatter(
|
||||||
|
"[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}"
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach(LogEvent logEvent in LogStore.Events)
|
||||||
|
AddLogEvent(logEvent);
|
||||||
|
|
||||||
|
this.WhenActivated(disp =>
|
||||||
|
{
|
||||||
|
LogStore.EventAdded += OnLogEventAdded;
|
||||||
|
|
||||||
|
Disposable.Create(() =>
|
||||||
|
{
|
||||||
|
LogStore.EventAdded -= OnLogEventAdded;
|
||||||
|
}).DisposeWith(disp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLogEventAdded(object? sender, LogEventEventArgs e)
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
AddLogEvent(e.LogEvent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddLogEvent(LogEvent logEvent)
|
||||||
|
{
|
||||||
|
using StringWriter writer = new();
|
||||||
|
_formatter.Format(logEvent, writer);
|
||||||
|
string line = writer.ToString();
|
||||||
|
Document.Insert(Document.TextLength, '\n' + line.TrimEnd('\r', '\n'));
|
||||||
|
while (Document.LineCount > MAX_ENTRIES)
|
||||||
|
RemoveOldestLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveOldestLine()
|
||||||
|
{
|
||||||
|
int firstNewLine = Document.Text.IndexOf('\n');
|
||||||
|
if (firstNewLine == -1)
|
||||||
|
{
|
||||||
|
//this should never happen.
|
||||||
|
//just in case let's return
|
||||||
|
//instead of throwing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Document.Remove(0, firstNewLine + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -25,20 +25,13 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Border Classes="card" Padding="10">
|
<Border Classes="card" Padding="10">
|
||||||
<ZoomBorder Name="ZoomBorder"
|
<Image Name="Visualization" Source="{Binding CurrentFrame}">
|
||||||
Stretch="None"
|
<Image.Transitions>
|
||||||
ClipToBounds="True"
|
<Transitions>
|
||||||
Focusable="True"
|
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.2" Easing="CubicEaseOut" />
|
||||||
VerticalAlignment="Stretch"
|
</Transitions>
|
||||||
HorizontalAlignment="Stretch">
|
</Image.Transitions>
|
||||||
<Image Name="Visualization" Source="{Binding CurrentFrame}">
|
</Image>
|
||||||
<Image.Transitions>
|
|
||||||
<Transitions>
|
|
||||||
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.2" Easing="CubicEaseOut" />
|
|
||||||
</Transitions>
|
|
||||||
</Image.Transitions>
|
|
||||||
</Image>
|
|
||||||
</ZoomBorder>
|
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -4,7 +4,7 @@ using System.Reactive;
|
|||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.DryIoc.Factories;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using RGB.NET.Core;
|
using RGB.NET.Core;
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.DryIoc.Factories;
|
||||||
using Artemis.UI.Screens.Settings;
|
using Artemis.UI.Screens.Settings;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
@ -82,7 +82,7 @@ public class DeviceSettingsViewModel : ActivatableViewModelBase
|
|||||||
|
|
||||||
await _windowService.CreateContentDialog()
|
await _windowService.CreateContentDialog()
|
||||||
.WithTitle($"{Device.RgbDevice.DeviceInfo.DeviceName} - Detect input")
|
.WithTitle($"{Device.RgbDevice.DeviceInfo.DeviceName} - Detect input")
|
||||||
.WithViewModel<DeviceDetectInputViewModel>(out DeviceDetectInputViewModel? viewModel, ("device", Device))
|
.WithViewModel(out DeviceDetectInputViewModel viewModel, Device)
|
||||||
.WithCloseButtonText("Cancel")
|
.WithCloseButtonText("Cancel")
|
||||||
.ShowAsync();
|
.ShowAsync();
|
||||||
|
|
||||||
|
|||||||
@ -24,8 +24,7 @@ public class DeviceLedsTabLedViewModel : ViewModelBase
|
|||||||
get => _isSelected;
|
get => _isSelected;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (!RaiseAndSetIfChanged(ref _isSelected, value))
|
RaiseAndSetIfChanged(ref _isSelected, value);
|
||||||
return;
|
|
||||||
Apply();
|
Apply();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,8 +14,14 @@
|
|||||||
<shared:ToStringConverter x:Key="ToStringConverter" />
|
<shared:ToStringConverter x:Key="ToStringConverter" />
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
<DataGrid Items="{CompiledBinding LedViewModels}" CanUserSortColumns="True" AutoGenerateColumns="False">
|
<DataGrid Items="{CompiledBinding LedViewModels}" CanUserSortColumns="True" AutoGenerateColumns="False">
|
||||||
<DataGrid.Columns>
|
<DataGrid.Columns >
|
||||||
<DataGridCheckBoxColumn Binding="{Binding IsSelected, Mode=TwoWay}" CanUserSort="False" CanUserReorder="False" Header="Highlight" />
|
<DataGridTemplateColumn CanUserSort="False" CanUserReorder="False" Header="Highlight">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
<DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Id, Converter={StaticResource LedIdToStringConverter}, Mode=OneWay}" Header="LED ID" Width="Auto" />
|
<DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Id, Converter={StaticResource LedIdToStringConverter}, Mode=OneWay}" Header="LED ID" Width="Auto" />
|
||||||
<DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Color, Converter={StaticResource ToStringConverter}, Mode=OneWay}" Header="Color (ARGB)" Width="Auto" CanUserSort="False" />
|
<DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Color, Converter={StaticResource ToStringConverter}, Mode=OneWay}" Header="Color (ARGB)" Width="Auto" CanUserSort="False" />
|
||||||
<DataGridTextColumn Binding="{Binding ArtemisLed.Layout.Image, Converter={StaticResource UriToFileNameConverter}, Mode=OneWay}" Header="Image file" CanUserSort="False" />
|
<DataGridTextColumn Binding="{Binding ArtemisLed.Layout.Image, Converter={StaticResource UriToFileNameConverter}, Mode=OneWay}" Header="Image file" CanUserSort="False" />
|
||||||
|
|||||||
@ -54,7 +54,7 @@ public class DeviceLogicalLayoutDialogViewModel : ContentDialogViewModelBase
|
|||||||
{
|
{
|
||||||
await windowService.CreateContentDialog()
|
await windowService.CreateContentDialog()
|
||||||
.WithTitle("Select logical layout")
|
.WithTitle("Select logical layout")
|
||||||
.WithViewModel(out DeviceLogicalLayoutDialogViewModel vm, ("device", device))
|
.WithViewModel(out DeviceLogicalLayoutDialogViewModel vm, device)
|
||||||
.WithCloseButtonText("Cancel")
|
.WithCloseButtonText("Cancel")
|
||||||
.WithDefaultButton(ContentDialogButton.Primary)
|
.WithDefaultButton(ContentDialogButton.Primary)
|
||||||
.HavingPrimaryButton(b => b.WithText("Select").WithCommand(vm.ApplyLogicalLayout))
|
.HavingPrimaryButton(b => b.WithText("Select").WithCommand(vm.ApplyLogicalLayout))
|
||||||
|
|||||||
@ -25,7 +25,7 @@ public class DevicePhysicalLayoutDialogViewModel : ContentDialogViewModelBase
|
|||||||
{
|
{
|
||||||
await windowService.CreateContentDialog()
|
await windowService.CreateContentDialog()
|
||||||
.WithTitle("Select physical layout")
|
.WithTitle("Select physical layout")
|
||||||
.WithViewModel(out DevicePhysicalLayoutDialogViewModel vm, ("device", device))
|
.WithViewModel(out DevicePhysicalLayoutDialogViewModel vm, device)
|
||||||
.WithCloseButtonText("Cancel")
|
.WithCloseButtonText("Cancel")
|
||||||
.ShowAsync();
|
.ShowAsync();
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ using System.Reactive.Disposables;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.DryIoc.Factories;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
@ -89,7 +89,7 @@ public class PluginPrerequisitesInstallDialogViewModel : ContentDialogViewModelB
|
|||||||
{
|
{
|
||||||
await windowService.CreateContentDialog()
|
await windowService.CreateContentDialog()
|
||||||
.WithTitle("Plugin prerequisites")
|
.WithTitle("Plugin prerequisites")
|
||||||
.WithViewModel(out PluginPrerequisitesInstallDialogViewModel vm, ("subjects", subjects))
|
.WithViewModel(out PluginPrerequisitesInstallDialogViewModel vm, subjects)
|
||||||
.WithCloseButtonText("Cancel")
|
.WithCloseButtonText("Cancel")
|
||||||
.HavingPrimaryButton(b => b.WithText("Install").WithCommand(vm.Install))
|
.HavingPrimaryButton(b => b.WithText("Install").WithCommand(vm.Install))
|
||||||
.WithDefaultButton(ContentDialogButton.Primary)
|
.WithDefaultButton(ContentDialogButton.Primary)
|
||||||
|
|||||||
@ -8,7 +8,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.DryIoc.Factories;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
@ -74,7 +74,7 @@ public class PluginPrerequisitesUninstallDialogViewModel : ContentDialogViewMode
|
|||||||
{
|
{
|
||||||
await windowService.CreateContentDialog()
|
await windowService.CreateContentDialog()
|
||||||
.WithTitle("Plugin prerequisites")
|
.WithTitle("Plugin prerequisites")
|
||||||
.WithViewModel(out PluginPrerequisitesUninstallDialogViewModel vm, ("subjects", subjects))
|
.WithViewModel(out PluginPrerequisitesUninstallDialogViewModel vm, subjects)
|
||||||
.WithCloseButtonText(cancelLabel)
|
.WithCloseButtonText(cancelLabel)
|
||||||
.HavingPrimaryButton(b => b.WithText("Uninstall").WithCommand(vm.Uninstall))
|
.HavingPrimaryButton(b => b.WithText("Uninstall").WithCommand(vm.Uninstall))
|
||||||
.WithDefaultButton(ContentDialogButton.Primary)
|
.WithDefaultButton(ContentDialogButton.Primary)
|
||||||
|
|||||||
@ -4,7 +4,7 @@ using System.Reactive;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.DryIoc.Factories;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|||||||
@ -13,8 +13,8 @@ using Artemis.UI.Shared.Services;
|
|||||||
using Artemis.UI.Shared.Services.Builders;
|
using Artemis.UI.Shared.Services.Builders;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
|
using DryIoc;
|
||||||
using Material.Icons;
|
using Material.Icons;
|
||||||
using Ninject;
|
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Plugins;
|
namespace Artemis.UI.Screens.Plugins;
|
||||||
@ -209,8 +209,7 @@ public class PluginViewModel : ActivatableViewModelBase
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
PluginConfigurationViewModel? viewModel = Plugin.Kernel!.Get(Plugin.ConfigurationDialog.Type) as PluginConfigurationViewModel;
|
if (Plugin.Resolve(Plugin.ConfigurationDialog.Type) is not PluginConfigurationViewModel viewModel)
|
||||||
if (viewModel == null)
|
|
||||||
throw new ArtemisUIException($"The type of a plugin configuration dialog must inherit {nameof(PluginConfigurationViewModel)}");
|
throw new ArtemisUIException($"The type of a plugin configuration dialog must inherit {nameof(PluginConfigurationViewModel)}");
|
||||||
|
|
||||||
_window = _windowService.ShowWindow(new PluginSettingsWindowViewModel(viewModel));
|
_window = _windowService.ShowWindow(new PluginSettingsWindowViewModel(viewModel));
|
||||||
|
|||||||
@ -78,7 +78,7 @@ public class EventConditionViewModel : ActivatableViewModelBase
|
|||||||
|
|
||||||
private async Task ExecuteOpenEditor()
|
private async Task ExecuteOpenEditor()
|
||||||
{
|
{
|
||||||
await _windowService.ShowDialogAsync<NodeScriptWindowViewModel, bool>(("nodeScript", _eventCondition.Script));
|
await _windowService.ShowDialogAsync<NodeScriptWindowViewModel, bool>(_eventCondition.Script);
|
||||||
await _profileEditorService.SaveProfileAsync();
|
await _profileEditorService.SaveProfileAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -51,7 +51,7 @@ public class StaticConditionViewModel : ActivatableViewModelBase
|
|||||||
|
|
||||||
private async Task ExecuteOpenEditor()
|
private async Task ExecuteOpenEditor()
|
||||||
{
|
{
|
||||||
await _windowService.ShowDialogAsync<NodeScriptWindowViewModel, bool>(("nodeScript", _staticCondition.Script));
|
await _windowService.ShowDialogAsync<NodeScriptWindowViewModel, bool>(_staticCondition.Script);
|
||||||
await _profileEditorService.SaveProfileAsync();
|
await _profileEditorService.SaveProfileAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2,7 +2,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.DryIoc.Factories;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||||
|
|||||||
@ -137,10 +137,7 @@ public class MenuBarViewModel : ActivatableViewModelBase
|
|||||||
if (ProfileConfiguration == null)
|
if (ProfileConfiguration == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await _windowService.ShowDialogAsync<ProfileConfigurationEditViewModel, ProfileConfiguration?>(
|
await _windowService.ShowDialogAsync<ProfileConfigurationEditViewModel, ProfileConfiguration?>(ProfileConfiguration.Category, ProfileConfiguration);
|
||||||
("profileCategory", ProfileConfiguration.Category),
|
|
||||||
("profileConfiguration", ProfileConfiguration)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExecuteViewScripts()
|
private async Task ExecuteViewScripts()
|
||||||
@ -148,7 +145,7 @@ public class MenuBarViewModel : ActivatableViewModelBase
|
|||||||
if (ProfileConfiguration?.Profile == null)
|
if (ProfileConfiguration?.Profile == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await _windowService.ShowDialogAsync<ScriptsDialogViewModel, object?>(("profile", ProfileConfiguration.Profile));
|
await _windowService.ShowDialogAsync<ScriptsDialogViewModel, object?>(ProfileConfiguration.Profile);
|
||||||
await _profileEditorService.SaveProfileAsync();
|
await _profileEditorService.SaveProfileAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ using System.Linq;
|
|||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.DryIoc.Factories;
|
||||||
using Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints;
|
using Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs.AdaptionHints;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Avalonia.Controls.Mixins;
|
using Avalonia.Controls.Mixins;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user