mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Plugins - Implemented features enable/disable UI
This commit is contained in:
parent
e812929215
commit
c146479393
@ -7,6 +7,7 @@ using LiteDB;
|
|||||||
using Ninject.Activation;
|
using Ninject.Activation;
|
||||||
using Ninject.Extensions.Conventions;
|
using Ninject.Extensions.Conventions;
|
||||||
using Ninject.Modules;
|
using Ninject.Modules;
|
||||||
|
using Ninject.Planning.Bindings.Resolvers;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Artemis.Core.Ninject
|
namespace Artemis.Core.Ninject
|
||||||
@ -22,6 +23,8 @@ namespace Artemis.Core.Ninject
|
|||||||
if (Kernel == null)
|
if (Kernel == null)
|
||||||
throw new ArtemisCoreException("Failed to bind Ninject Core module, kernel is null.");
|
throw new ArtemisCoreException("Failed to bind Ninject Core module, kernel is null.");
|
||||||
|
|
||||||
|
Kernel.Components.Remove<IMissingBindingResolver, SelfBindingResolver>();
|
||||||
|
|
||||||
// Bind all services as singletons
|
// Bind all services as singletons
|
||||||
Kernel.Bind(x =>
|
Kernel.Bind(x =>
|
||||||
{
|
{
|
||||||
@ -88,6 +91,7 @@ namespace Artemis.Core.Ninject
|
|||||||
|
|
||||||
Kernel.Bind<PluginSettings>().ToProvider<PluginSettingsProvider>();
|
Kernel.Bind<PluginSettings>().ToProvider<PluginSettingsProvider>();
|
||||||
Kernel.Bind<ILogger>().ToProvider<LoggerProvider>();
|
Kernel.Bind<ILogger>().ToProvider<LoggerProvider>();
|
||||||
|
Kernel.Bind<LoggerProvider>().ToSelf();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool HasAccessToProtectedService(IRequest r)
|
private bool HasAccessToProtectedService(IRequest r)
|
||||||
|
|||||||
@ -1,19 +1,52 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
using Ninject.Extensions.Conventions;
|
||||||
using Ninject.Modules;
|
using Ninject.Modules;
|
||||||
|
using Ninject.Planning.Bindings.Resolvers;
|
||||||
|
|
||||||
namespace Artemis.Core.Ninject
|
namespace Artemis.Core.Ninject
|
||||||
{
|
{
|
||||||
internal class PluginModule : NinjectModule
|
internal class PluginModule : NinjectModule
|
||||||
{
|
{
|
||||||
public PluginModule(PluginInfo pluginInfo)
|
public PluginModule(Plugin plugin)
|
||||||
{
|
{
|
||||||
PluginInfo = pluginInfo ?? throw new ArgumentNullException(nameof(pluginInfo));
|
Plugin = plugin ?? throw new ArgumentNullException(nameof(plugin));
|
||||||
}
|
}
|
||||||
|
|
||||||
public PluginInfo PluginInfo { get; }
|
public Plugin Plugin { get; }
|
||||||
|
|
||||||
public override void Load()
|
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);
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -13,7 +13,7 @@ namespace Artemis.Core.Ninject
|
|||||||
private readonly IPluginRepository _pluginRepository;
|
private readonly IPluginRepository _pluginRepository;
|
||||||
private readonly IPluginManagementService _pluginManagementService;
|
private readonly IPluginManagementService _pluginManagementService;
|
||||||
|
|
||||||
internal PluginSettingsProvider(IPluginRepository pluginRepository, IPluginManagementService pluginManagementService)
|
public PluginSettingsProvider(IPluginRepository pluginRepository, IPluginManagementService pluginManagementService)
|
||||||
{
|
{
|
||||||
_pluginRepository = pluginRepository;
|
_pluginRepository = pluginRepository;
|
||||||
_pluginManagementService = pluginManagementService;
|
_pluginManagementService = pluginManagementService;
|
||||||
|
|||||||
@ -20,7 +20,7 @@ namespace Artemis.Core.DataModelExpansions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ReadOnlyCollection<PropertyInfo> HiddenProperties => HiddenPropertiesList.AsReadOnly();
|
public ReadOnlyCollection<PropertyInfo> HiddenProperties => HiddenPropertiesList.AsReadOnly();
|
||||||
|
|
||||||
internal DataModel InternalDataModel { get; set; }
|
internal DataModel? InternalDataModel { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called each frame when the data model should update
|
/// Called each frame when the data model should update
|
||||||
@ -28,6 +28,12 @@ namespace Artemis.Core.DataModelExpansions
|
|||||||
/// <param name="deltaTime">Time in seconds since the last update</param>
|
/// <param name="deltaTime">Time in seconds since the last update</param>
|
||||||
public abstract void Update(double deltaTime);
|
public abstract void Update(double deltaTime);
|
||||||
|
|
||||||
|
internal void InternalUpdate(double deltaTime)
|
||||||
|
{
|
||||||
|
if (InternalDataModel != null)
|
||||||
|
Update(deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Override to provide your own data model description. By default this returns a description matching your plugin
|
/// Override to provide your own data model description. By default this returns a description matching your plugin
|
||||||
/// name and description
|
/// name and description
|
||||||
|
|||||||
@ -129,14 +129,15 @@ namespace Artemis.Core
|
|||||||
|
|
||||||
internal void RemoveFeature(PluginFeature feature)
|
internal void RemoveFeature(PluginFeature feature)
|
||||||
{
|
{
|
||||||
|
if (feature.IsEnabled)
|
||||||
|
throw new ArtemisCoreException("Cannot remove an enabled feature from a plugin");
|
||||||
|
|
||||||
_features.Remove(feature);
|
_features.Remove(feature);
|
||||||
feature.InternalDisable();
|
|
||||||
feature.Dispose();
|
feature.Dispose();
|
||||||
|
|
||||||
OnFeatureRemoved(new PluginFeatureEventArgs(feature));
|
OnFeatureRemoved(new PluginFeatureEventArgs(feature));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal void SetEnabled(bool enable)
|
internal void SetEnabled(bool enable)
|
||||||
{
|
{
|
||||||
if (IsEnabled == enable)
|
if (IsEnabled == enable)
|
||||||
|
|||||||
@ -87,8 +87,8 @@ namespace Artemis.Core
|
|||||||
throw new ArtemisPluginLockException(LoadException);
|
throw new ArtemisPluginLockException(LoadException);
|
||||||
}
|
}
|
||||||
|
|
||||||
IsEnabled = true;
|
|
||||||
CreateLockFile();
|
CreateLockFile();
|
||||||
|
IsEnabled = true;
|
||||||
|
|
||||||
// Allow up to 15 seconds for plugins to activate.
|
// Allow up to 15 seconds for plugins to activate.
|
||||||
// This means plugins that need more time should do their long running tasks in a background thread, which is intentional
|
// This means plugins that need more time should do their long running tasks in a background thread, which is intentional
|
||||||
|
|||||||
@ -163,7 +163,7 @@ namespace Artemis.Core.Services
|
|||||||
{
|
{
|
||||||
// Update all active modules, check Enabled status because it may go false before before the _dataModelExpansions list is updated
|
// Update all active modules, check Enabled status because it may go false before before the _dataModelExpansions list is updated
|
||||||
foreach (BaseDataModelExpansion dataModelExpansion in _dataModelExpansions.Where(e => e.IsEnabled))
|
foreach (BaseDataModelExpansion dataModelExpansion in _dataModelExpansions.Where(e => e.IsEnabled))
|
||||||
dataModelExpansion.Update(args.DeltaTime);
|
dataModelExpansion.InternalUpdate(args.DeltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Module> modules;
|
List<Module> modules;
|
||||||
|
|||||||
@ -43,7 +43,8 @@ namespace Artemis.Core.Services
|
|||||||
/// Enables the provided <paramref name="plugin" />
|
/// Enables the provided <paramref name="plugin" />
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="plugin">The plugin to enable</param>
|
/// <param name="plugin">The plugin to enable</param>
|
||||||
void EnablePlugin(Plugin plugin, bool ignorePluginLock = false);
|
/// <param name="saveState">Whether or not to save the new enabled state</param>
|
||||||
|
void EnablePlugin(Plugin plugin, bool saveState, bool ignorePluginLock = false);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unloads the provided <paramref name="plugin" />
|
/// Unloads the provided <paramref name="plugin" />
|
||||||
@ -55,20 +56,23 @@ namespace Artemis.Core.Services
|
|||||||
/// Disables the provided <paramref name="plugin" />
|
/// Disables the provided <paramref name="plugin" />
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="plugin">The plugin to disable</param>
|
/// <param name="plugin">The plugin to disable</param>
|
||||||
void DisablePlugin(Plugin plugin);
|
/// <param name="saveState">Whether or not to save the new enabled state</param>
|
||||||
|
void DisablePlugin(Plugin plugin, bool saveState);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables the provided plugin feature
|
/// Enables the provided plugin feature
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pluginFeature"></param>
|
/// <param name="pluginFeature">The feature to enable</param>
|
||||||
|
/// <param name="saveState">Whether or not to save the new enabled state</param>
|
||||||
/// <param name="isAutoEnable">If true, fails if there is a lock file present</param>
|
/// <param name="isAutoEnable">If true, fails if there is a lock file present</param>
|
||||||
void EnablePluginFeature(PluginFeature pluginFeature, bool isAutoEnable = false);
|
void EnablePluginFeature(PluginFeature pluginFeature, bool saveState, bool isAutoEnable = false);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disables the provided plugin feature
|
/// Disables the provided plugin feature
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pluginFeature"></param>
|
/// <param name="pluginFeature">The feature to enable</param>
|
||||||
void DisablePluginFeature(PluginFeature pluginFeature);
|
/// <param name="saveState">Whether or not to save the new enabled state</param>
|
||||||
|
void DisablePluginFeature(PluginFeature pluginFeature, bool saveState);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the plugin info of all loaded plugins
|
/// Gets the plugin info of all loaded plugins
|
||||||
|
|||||||
13
src/Artemis.Core/Services/Interfaces/IPluginService.cs
Normal file
13
src/Artemis.Core/Services/Interfaces/IPluginService.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
namespace Artemis.Core.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An interface for services provided by plugins.
|
||||||
|
/// <para>
|
||||||
|
/// Any service implementing this interface will be available inside the plugin as a singleton through dependency
|
||||||
|
/// injection
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public interface IPluginService
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -185,7 +185,8 @@ namespace Artemis.Core.Services
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Plugin plugin = LoadPlugin(subDirectory);
|
Plugin plugin = LoadPlugin(subDirectory);
|
||||||
EnablePlugin(plugin, ignorePluginLock);
|
if (plugin.Entity.IsEnabled)
|
||||||
|
EnablePlugin(plugin, false, ignorePluginLock);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -271,16 +272,13 @@ namespace Artemis.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EnablePlugin(Plugin plugin, bool ignorePluginLock)
|
public void EnablePlugin(Plugin plugin, bool saveState, bool ignorePluginLock)
|
||||||
{
|
{
|
||||||
if (plugin.Assembly == null)
|
if (plugin.Assembly == null)
|
||||||
throw new ArtemisPluginException(plugin, "Cannot enable a plugin that hasn't successfully been loaded");
|
throw new ArtemisPluginException(plugin, "Cannot enable a plugin that hasn't successfully been loaded");
|
||||||
|
|
||||||
// Create the Ninject child kernel and load the module
|
// Create the Ninject child kernel and load the module
|
||||||
plugin.Kernel = new ChildKernel(_kernel);
|
plugin.Kernel = new ChildKernel(_kernel, new PluginModule(plugin));
|
||||||
plugin.Kernel.Bind<Plugin>().ToConstant(plugin);
|
|
||||||
plugin.Kernel.Load(new PluginModule(plugin.Info));
|
|
||||||
|
|
||||||
plugin.SetEnabled(true);
|
plugin.SetEnabled(true);
|
||||||
|
|
||||||
// Get the Plugin feature from the main assembly and if there is only one, instantiate it
|
// Get the Plugin feature from the main assembly and if there is only one, instantiate it
|
||||||
@ -303,6 +301,8 @@ namespace Artemis.Core.Services
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
plugin.Kernel.Bind(featureType).ToSelf().InSingletonScope();
|
||||||
|
|
||||||
// Include Plugin as a parameter for the PluginSettingsProvider
|
// Include Plugin as a parameter for the PluginSettingsProvider
|
||||||
IParameter[] parameters = {new Parameter("Plugin", plugin, false)};
|
IParameter[] parameters = {new Parameter("Plugin", plugin, false)};
|
||||||
PluginFeature instance = (PluginFeature) plugin.Kernel.Get(featureType, parameters);
|
PluginFeature instance = (PluginFeature) plugin.Kernel.Get(featureType, parameters);
|
||||||
@ -314,7 +314,7 @@ namespace Artemis.Core.Services
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
throw new ArtemisPluginException(plugin, "Failed to load instantiate feature", e);
|
throw new ArtemisPluginException(plugin, "Failed to instantiate feature", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,7 +323,7 @@ namespace Artemis.Core.Services
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
EnablePluginFeature(pluginFeature, !ignorePluginLock);
|
EnablePluginFeature(pluginFeature, false, !ignorePluginLock);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
@ -331,6 +331,12 @@ namespace Artemis.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (saveState)
|
||||||
|
{
|
||||||
|
plugin.Entity.IsEnabled = plugin.IsEnabled;
|
||||||
|
SavePlugin(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
OnPluginEnabled(new PluginEventArgs(plugin));
|
OnPluginEnabled(new PluginEventArgs(plugin));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,7 +346,7 @@ namespace Artemis.Core.Services
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
DisablePlugin(plugin);
|
DisablePlugin(plugin, false);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -356,7 +362,7 @@ namespace Artemis.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DisablePlugin(Plugin plugin)
|
public void DisablePlugin(Plugin plugin, bool saveState)
|
||||||
{
|
{
|
||||||
if (!plugin.IsEnabled)
|
if (!plugin.IsEnabled)
|
||||||
return;
|
return;
|
||||||
@ -364,14 +370,25 @@ namespace Artemis.Core.Services
|
|||||||
while (plugin.Features.Any())
|
while (plugin.Features.Any())
|
||||||
{
|
{
|
||||||
PluginFeature feature = plugin.Features[0];
|
PluginFeature feature = plugin.Features[0];
|
||||||
|
if (feature.IsEnabled)
|
||||||
|
DisablePluginFeature(feature, false);
|
||||||
plugin.RemoveFeature(feature);
|
plugin.RemoveFeature(feature);
|
||||||
OnPluginFeatureDisabled(new PluginFeatureEventArgs(feature));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin.SetEnabled(false);
|
plugin.SetEnabled(false);
|
||||||
|
|
||||||
plugin.Kernel.Dispose();
|
plugin.Kernel.Dispose();
|
||||||
plugin.Kernel = null;
|
plugin.Kernel = null;
|
||||||
|
|
||||||
|
GC.Collect();
|
||||||
|
GC.WaitForPendingFinalizers();
|
||||||
|
GC.Collect();
|
||||||
|
|
||||||
|
if (saveState)
|
||||||
|
{
|
||||||
|
plugin.Entity.IsEnabled = plugin.IsEnabled;
|
||||||
|
SavePlugin(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
OnPluginDisabled(new PluginEventArgs(plugin));
|
OnPluginDisabled(new PluginEventArgs(plugin));
|
||||||
}
|
}
|
||||||
@ -380,7 +397,7 @@ namespace Artemis.Core.Services
|
|||||||
|
|
||||||
#region Features
|
#region Features
|
||||||
|
|
||||||
public void EnablePluginFeature(PluginFeature pluginFeature, bool isAutoEnable = false)
|
public void EnablePluginFeature(PluginFeature pluginFeature, bool saveState, bool isAutoEnable)
|
||||||
{
|
{
|
||||||
_logger.Debug("Enabling plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin);
|
_logger.Debug("Enabling plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin);
|
||||||
|
|
||||||
@ -389,16 +406,9 @@ namespace Artemis.Core.Services
|
|||||||
OnPluginFeatureEnabling(new PluginFeatureEventArgs(pluginFeature));
|
OnPluginFeatureEnabling(new PluginFeatureEventArgs(pluginFeature));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// A device provider may be queued for disable on next restart, this undoes that
|
|
||||||
if (pluginFeature is DeviceProvider && pluginFeature.IsEnabled && !pluginFeature.Entity.IsEnabled)
|
|
||||||
{
|
|
||||||
pluginFeature.Entity.IsEnabled = true;
|
|
||||||
SavePlugin(pluginFeature.Plugin);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginFeature.SetEnabled(true, isAutoEnable);
|
pluginFeature.SetEnabled(true, isAutoEnable);
|
||||||
pluginFeature.Entity.IsEnabled = true;
|
if (saveState)
|
||||||
|
pluginFeature.Entity.IsEnabled = true;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -412,10 +422,13 @@ namespace Artemis.Core.Services
|
|||||||
{
|
{
|
||||||
// On an auto-enable, ensure PluginInfo.Enabled is true even if enable failed, that way a failure on auto-enable does
|
// On an auto-enable, ensure PluginInfo.Enabled is true even if enable failed, that way a failure on auto-enable does
|
||||||
// not affect the user's settings
|
// not affect the user's settings
|
||||||
if (isAutoEnable)
|
if (saveState)
|
||||||
pluginFeature.Entity.IsEnabled = true;
|
{
|
||||||
|
if (isAutoEnable)
|
||||||
|
pluginFeature.Entity.IsEnabled = true;
|
||||||
|
|
||||||
SavePlugin(pluginFeature.Plugin);
|
SavePlugin(pluginFeature.Plugin);
|
||||||
|
}
|
||||||
|
|
||||||
if (pluginFeature.IsEnabled)
|
if (pluginFeature.IsEnabled)
|
||||||
{
|
{
|
||||||
@ -430,34 +443,22 @@ namespace Artemis.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DisablePluginFeature(PluginFeature pluginFeature)
|
public void DisablePluginFeature(PluginFeature pluginFeature, bool saveState)
|
||||||
{
|
{
|
||||||
lock (_plugins)
|
lock (_plugins)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.Debug("Disabling plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin);
|
_logger.Debug("Disabling plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin);
|
||||||
|
|
||||||
// Device providers cannot be disabled at runtime simply queue a disable for next restart
|
|
||||||
if (pluginFeature is DeviceProvider)
|
|
||||||
{
|
|
||||||
// Don't call SetEnabled(false) but simply update enabled state and save it
|
|
||||||
pluginFeature.Entity.IsEnabled = false;
|
|
||||||
SavePlugin(pluginFeature.Plugin);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginFeature.SetEnabled(false);
|
pluginFeature.SetEnabled(false);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Console.WriteLine(e);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
pluginFeature.Entity.IsEnabled = false;
|
if (saveState)
|
||||||
SavePlugin(pluginFeature.Plugin);
|
{
|
||||||
|
pluginFeature.Entity.IsEnabled = false;
|
||||||
|
SavePlugin(pluginFeature.Plugin);
|
||||||
|
}
|
||||||
|
|
||||||
if (!pluginFeature.IsEnabled)
|
if (!pluginFeature.IsEnabled)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -7,7 +7,7 @@ namespace Artemis.Core.Services
|
|||||||
{
|
{
|
||||||
private readonly PluginSettings _pluginSettings;
|
private readonly PluginSettings _pluginSettings;
|
||||||
|
|
||||||
internal SettingsService(IPluginRepository pluginRepository)
|
public SettingsService(IPluginRepository pluginRepository)
|
||||||
{
|
{
|
||||||
_pluginSettings = new PluginSettings(Constants.CorePlugin, pluginRepository);
|
_pluginSettings = new PluginSettings(Constants.CorePlugin, pluginRepository);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ namespace Artemis.Core.Services
|
|||||||
private readonly IProfileRepository _profileRepository;
|
private readonly IProfileRepository _profileRepository;
|
||||||
private readonly ISurfaceService _surfaceService;
|
private readonly ISurfaceService _surfaceService;
|
||||||
|
|
||||||
internal ProfileService(ILogger logger,
|
public ProfileService(ILogger logger,
|
||||||
IPluginManagementService pluginManagementService,
|
IPluginManagementService pluginManagementService,
|
||||||
ISurfaceService surfaceService,
|
ISurfaceService surfaceService,
|
||||||
IConditionOperatorService conditionOperatorService,
|
IConditionOperatorService conditionOperatorService,
|
||||||
|
|||||||
@ -6,11 +6,11 @@ using LiteDB;
|
|||||||
|
|
||||||
namespace Artemis.Storage.Repositories
|
namespace Artemis.Storage.Repositories
|
||||||
{
|
{
|
||||||
public class ModuleRepository : IModuleRepository
|
internal class ModuleRepository : IModuleRepository
|
||||||
{
|
{
|
||||||
private readonly LiteRepository _repository;
|
private readonly LiteRepository _repository;
|
||||||
|
|
||||||
internal ModuleRepository(LiteRepository repository)
|
public ModuleRepository(LiteRepository repository)
|
||||||
{
|
{
|
||||||
_repository = repository;
|
_repository = repository;
|
||||||
_repository.Database.GetCollection<ModuleSettingsEntity>().EnsureIndex(s => s.ModuleId, true);
|
_repository.Database.GetCollection<ModuleSettingsEntity>().EnsureIndex(s => s.ModuleId, true);
|
||||||
|
|||||||
@ -5,11 +5,11 @@ using LiteDB;
|
|||||||
|
|
||||||
namespace Artemis.Storage.Repositories
|
namespace Artemis.Storage.Repositories
|
||||||
{
|
{
|
||||||
public class PluginRepository : IPluginRepository
|
internal class PluginRepository : IPluginRepository
|
||||||
{
|
{
|
||||||
private readonly LiteRepository _repository;
|
private readonly LiteRepository _repository;
|
||||||
|
|
||||||
internal PluginRepository(LiteRepository repository)
|
public PluginRepository(LiteRepository repository)
|
||||||
{
|
{
|
||||||
_repository = repository;
|
_repository = repository;
|
||||||
|
|
||||||
|
|||||||
@ -6,11 +6,11 @@ using LiteDB;
|
|||||||
|
|
||||||
namespace Artemis.Storage.Repositories
|
namespace Artemis.Storage.Repositories
|
||||||
{
|
{
|
||||||
public class ProfileRepository : IProfileRepository
|
internal class ProfileRepository : IProfileRepository
|
||||||
{
|
{
|
||||||
private readonly LiteRepository _repository;
|
private readonly LiteRepository _repository;
|
||||||
|
|
||||||
internal ProfileRepository(LiteRepository repository)
|
public ProfileRepository(LiteRepository repository)
|
||||||
{
|
{
|
||||||
_repository = repository;
|
_repository = repository;
|
||||||
_repository.Database.GetCollection<ProfileEntity>().EnsureIndex(s => s.Name);
|
_repository.Database.GetCollection<ProfileEntity>().EnsureIndex(s => s.Name);
|
||||||
|
|||||||
@ -5,11 +5,11 @@ using LiteDB;
|
|||||||
|
|
||||||
namespace Artemis.Storage.Repositories
|
namespace Artemis.Storage.Repositories
|
||||||
{
|
{
|
||||||
public class SurfaceRepository : ISurfaceRepository
|
internal class SurfaceRepository : ISurfaceRepository
|
||||||
{
|
{
|
||||||
private readonly LiteRepository _repository;
|
private readonly LiteRepository _repository;
|
||||||
|
|
||||||
internal SurfaceRepository(LiteRepository repository)
|
public SurfaceRepository(LiteRepository repository)
|
||||||
{
|
{
|
||||||
_repository = repository;
|
_repository = repository;
|
||||||
_repository.Database.GetCollection<SurfaceEntity>().EnsureIndex(s => s.Name);
|
_repository.Database.GetCollection<SurfaceEntity>().EnsureIndex(s => s.Name);
|
||||||
|
|||||||
@ -4,7 +4,7 @@ using Microsoft.Xaml.Behaviors;
|
|||||||
|
|
||||||
namespace Artemis.UI.Shared
|
namespace Artemis.UI.Shared
|
||||||
{
|
{
|
||||||
public class PutCursorAtEndTextBoxBehavior : Behavior<UIElement>
|
public class PutCursorAtEndTextBox : Behavior<UIElement>
|
||||||
{
|
{
|
||||||
private TextBox _textBox;
|
private TextBox _textBox;
|
||||||
|
|
||||||
|
|||||||
53
src/Artemis.UI.Shared/Behaviors/ScrollParentWhenAtMax.cs
Normal file
53
src/Artemis.UI.Shared/Behaviors/ScrollParentWhenAtMax.cs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using Microsoft.Xaml.Behaviors;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared
|
||||||
|
{
|
||||||
|
public class ScrollParentWhenAtMax : Behavior<FrameworkElement>
|
||||||
|
{
|
||||||
|
protected override void OnAttached()
|
||||||
|
{
|
||||||
|
base.OnAttached();
|
||||||
|
AssociatedObject.PreviewMouseWheel += PreviewMouseWheel;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDetaching()
|
||||||
|
{
|
||||||
|
AssociatedObject.PreviewMouseWheel -= PreviewMouseWheel;
|
||||||
|
base.OnDetaching();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PreviewMouseWheel(object sender, MouseWheelEventArgs e)
|
||||||
|
{
|
||||||
|
ScrollViewer scrollViewer = GetVisualChild<ScrollViewer>(AssociatedObject);
|
||||||
|
double scrollPos = scrollViewer.ContentVerticalOffset;
|
||||||
|
if (scrollPos == scrollViewer.ScrollableHeight && e.Delta < 0
|
||||||
|
|| scrollPos == 0 && e.Delta > 0)
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
MouseWheelEventArgs e2 = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta);
|
||||||
|
e2.RoutedEvent = UIElement.MouseWheelEvent;
|
||||||
|
AssociatedObject.RaiseEvent(e2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static T GetVisualChild<T>(DependencyObject parent) where T : Visual
|
||||||
|
{
|
||||||
|
T child = default;
|
||||||
|
|
||||||
|
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
|
||||||
|
for (int i = 0; i < numVisuals; i++)
|
||||||
|
{
|
||||||
|
Visual v = (Visual) VisualTreeHelper.GetChild(parent, i);
|
||||||
|
child = v as T;
|
||||||
|
if (child == null) child = GetVisualChild<T>(v);
|
||||||
|
if (child != null) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,11 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
using Stylet;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared
|
namespace Artemis.UI.Shared
|
||||||
{
|
{
|
||||||
@ -14,13 +14,13 @@ namespace Artemis.UI.Shared
|
|||||||
/// Converts <see cref="T:Artemis.Core.Models.Profile.ColorGradient" /> into a
|
/// Converts <see cref="T:Artemis.Core.Models.Profile.ColorGradient" /> into a
|
||||||
/// <see cref="T:System.Windows.Media.GradientStopCollection" />.
|
/// <see cref="T:System.Windows.Media.GradientStopCollection" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ValueConversion(typeof(BindableCollection<ColorGradientStop>), typeof(GradientStopCollection))]
|
[ValueConversion(typeof(List<ColorGradientStop>), typeof(GradientStopCollection))]
|
||||||
public class ColorGradientToGradientStopsConverter : IValueConverter
|
public class ColorGradientToGradientStopsConverter : IValueConverter
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
{
|
{
|
||||||
BindableCollection<ColorGradientStop> colorGradients = (BindableCollection<ColorGradientStop>) value;
|
List<ColorGradientStop> colorGradients = (List<ColorGradientStop>) value;
|
||||||
GradientStopCollection collection = new GradientStopCollection();
|
GradientStopCollection collection = new GradientStopCollection();
|
||||||
if (colorGradients == null)
|
if (colorGradients == null)
|
||||||
return collection;
|
return collection;
|
||||||
@ -34,7 +34,7 @@ namespace Artemis.UI.Shared
|
|||||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
{
|
{
|
||||||
GradientStopCollection collection = (GradientStopCollection) value;
|
GradientStopCollection collection = (GradientStopCollection) value;
|
||||||
BindableCollection<ColorGradientStop> colorGradients = new BindableCollection<ColorGradientStop>();
|
List<ColorGradientStop> colorGradients = new List<ColorGradientStop>();
|
||||||
if (collection == null)
|
if (collection == null)
|
||||||
return colorGradients;
|
return colorGradients;
|
||||||
|
|
||||||
|
|||||||
@ -33,7 +33,7 @@ namespace Artemis.UI.Shared.Services
|
|||||||
public DataModelPropertiesViewModel GetMainDataModelVisualization()
|
public DataModelPropertiesViewModel GetMainDataModelVisualization()
|
||||||
{
|
{
|
||||||
DataModelPropertiesViewModel viewModel = new DataModelPropertiesViewModel(null, null, null);
|
DataModelPropertiesViewModel viewModel = new DataModelPropertiesViewModel(null, null, null);
|
||||||
foreach (DataModel dataModelExpansion in _dataModelService.GetDataModels())
|
foreach (DataModel dataModelExpansion in _dataModelService.GetDataModels().OrderBy(d => d.DataModelDescription.Name))
|
||||||
viewModel.Children.Add(new DataModelPropertiesViewModel(dataModelExpansion, viewModel, new DataModelPath(dataModelExpansion)));
|
viewModel.Children.Add(new DataModelPropertiesViewModel(dataModelExpansion, viewModel, new DataModelPath(dataModelExpansion)));
|
||||||
|
|
||||||
// Update to populate children
|
// Update to populate children
|
||||||
@ -42,6 +42,25 @@ namespace Artemis.UI.Shared.Services
|
|||||||
return viewModel;
|
return viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateModules(DataModelPropertiesViewModel mainDataModelVisualization)
|
||||||
|
{
|
||||||
|
List<DataModelVisualizationViewModel> disabledChildren = mainDataModelVisualization.Children.Where(d => !d.DataModel.Feature.IsEnabled).ToList();
|
||||||
|
foreach (DataModelVisualizationViewModel child in disabledChildren)
|
||||||
|
mainDataModelVisualization.Children.Remove(child);
|
||||||
|
|
||||||
|
foreach (DataModel dataModelExpansion in _dataModelService.GetDataModels().OrderBy(d => d.DataModelDescription.Name))
|
||||||
|
{
|
||||||
|
if (mainDataModelVisualization.Children.All(c => c.DataModel != dataModelExpansion))
|
||||||
|
{
|
||||||
|
mainDataModelVisualization.Children.Add(
|
||||||
|
new DataModelPropertiesViewModel(dataModelExpansion, mainDataModelVisualization, new DataModelPath(dataModelExpansion))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mainDataModelVisualization.Update(this, null);
|
||||||
|
}
|
||||||
|
|
||||||
public DataModelPropertiesViewModel GetPluginDataModelVisualization(PluginFeature pluginFeature, bool includeMainDataModel)
|
public DataModelPropertiesViewModel GetPluginDataModelVisualization(PluginFeature pluginFeature, bool includeMainDataModel)
|
||||||
{
|
{
|
||||||
if (includeMainDataModel)
|
if (includeMainDataModel)
|
||||||
@ -64,7 +83,7 @@ namespace Artemis.UI.Shared.Services
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
DataModelPropertiesViewModel viewModel = new DataModelPropertiesViewModel(null, null, null);
|
DataModelPropertiesViewModel viewModel = new DataModelPropertiesViewModel(null, null, null);
|
||||||
viewModel.Children.Add(new DataModelPropertiesViewModel(dataModel, viewModel, null));
|
viewModel.Children.Add(new DataModelPropertiesViewModel(dataModel, viewModel, new DataModelPath(dataModel)));
|
||||||
|
|
||||||
// Update to populate children
|
// Update to populate children
|
||||||
viewModel.Update(this, null);
|
viewModel.Update(this, null);
|
||||||
|
|||||||
@ -7,22 +7,114 @@ using Artemis.UI.Shared.Input;
|
|||||||
|
|
||||||
namespace Artemis.UI.Shared.Services
|
namespace Artemis.UI.Shared.Services
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A service for UI related data model tasks
|
||||||
|
/// </summary>
|
||||||
public interface IDataModelUIService : IArtemisSharedUIService
|
public interface IDataModelUIService : IArtemisSharedUIService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a read-only list of all registered data model editors
|
||||||
|
/// </summary>
|
||||||
IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelEditors { get; }
|
IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelEditors { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a read-only list of all registered data model displays
|
||||||
|
/// </summary>
|
||||||
IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelDisplays { get; }
|
IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelDisplays { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a data model visualization view model for the main data model
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// A data model visualization view model containing all data model expansions and modules that expand the main
|
||||||
|
/// data model
|
||||||
|
/// </returns>
|
||||||
DataModelPropertiesViewModel GetMainDataModelVisualization();
|
DataModelPropertiesViewModel GetMainDataModelVisualization();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a data model visualization view model for the data model of the provided plugin feature
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pluginFeature">The plugin feature to create hte data model visualization view model for</param>
|
||||||
|
/// <param name="includeMainDataModel">Whether or not also to include the main data model</param>
|
||||||
|
/// <returns>A data model visualization view model containing the data model of the provided feature</returns>
|
||||||
DataModelPropertiesViewModel GetPluginDataModelVisualization(PluginFeature pluginFeature, bool includeMainDataModel);
|
DataModelPropertiesViewModel GetPluginDataModelVisualization(PluginFeature pluginFeature, bool includeMainDataModel);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the children of the provided main data model visualization, removing disabled children and adding newly
|
||||||
|
/// enabled children
|
||||||
|
/// </summary>
|
||||||
|
void UpdateModules(DataModelPropertiesViewModel mainDataModelVisualization);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a new data model editor
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the editor</typeparam>
|
||||||
|
/// <param name="plugin">The plugin this editor belongs to</param>
|
||||||
|
/// <param name="compatibleConversionTypes">A collection of extra types this editor supports</param>
|
||||||
|
/// <returns>A registration that can be used to remove the editor</returns>
|
||||||
DataModelVisualizationRegistration RegisterDataModelInput<T>(Plugin plugin, IReadOnlyCollection<Type> compatibleConversionTypes) where T : DataModelInputViewModel;
|
DataModelVisualizationRegistration RegisterDataModelInput<T>(Plugin plugin, IReadOnlyCollection<Type> compatibleConversionTypes) where T : DataModelInputViewModel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a new data model display
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the display</typeparam>
|
||||||
|
/// <param name="plugin">The plugin this display belongs to</param>
|
||||||
|
/// <returns>A registration that can be used to remove the display</returns>
|
||||||
DataModelVisualizationRegistration RegisterDataModelDisplay<T>(Plugin plugin) where T : DataModelDisplayViewModel;
|
DataModelVisualizationRegistration RegisterDataModelDisplay<T>(Plugin plugin) where T : DataModelDisplayViewModel;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a data model editor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="registration">
|
||||||
|
/// The registration of the editor as returned by <see cref="RegisterDataModelInput{T}" />
|
||||||
|
/// </param>
|
||||||
void RemoveDataModelInput(DataModelVisualizationRegistration registration);
|
void RemoveDataModelInput(DataModelVisualizationRegistration registration);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a data model display
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="registration">
|
||||||
|
/// The registration of the display as returned by <see cref="RegisterDataModelDisplay{T}" />
|
||||||
|
/// </param>
|
||||||
void RemoveDataModelDisplay(DataModelVisualizationRegistration registration);
|
void RemoveDataModelDisplay(DataModelVisualizationRegistration registration);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates the most appropriate display view model for the provided <paramref name="propertyType" /> that can display
|
||||||
|
/// a value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="propertyType">The type of data model property to find a display view model for</param>
|
||||||
|
/// <param name="description">The description of the data model property</param>
|
||||||
|
/// <param name="fallBackToDefault">
|
||||||
|
/// If <see langword="true"></see>, a simple .ToString() display view model will be
|
||||||
|
/// returned if nothing else is found
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The most appropriate display view model for the provided <paramref name="propertyType"></paramref></returns>
|
||||||
DataModelDisplayViewModel GetDataModelDisplayViewModel(Type propertyType, DataModelPropertyAttribute description, bool fallBackToDefault = false);
|
DataModelDisplayViewModel GetDataModelDisplayViewModel(Type propertyType, DataModelPropertyAttribute description, bool fallBackToDefault = false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates the most appropriate input view model for the provided <paramref name="propertyType" /> that allows
|
||||||
|
/// inputting a value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="propertyType">The type of data model property to find a display view model for</param>
|
||||||
|
/// <param name="description">The description of the data model property</param>
|
||||||
|
/// <param name="initialValue">The initial value to show in the input</param>
|
||||||
|
/// <param name="updateCallback">A function to call whenever the input was updated (submitted or not)</param>
|
||||||
|
/// <returns>The most appropriate input view model for the provided <paramref name="propertyType" /></returns>
|
||||||
DataModelInputViewModel GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute description, object initialValue, Action<object, bool> updateCallback);
|
DataModelInputViewModel GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute description, object initialValue, Action<object, bool> updateCallback);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a view model that allows selecting a value from the data model
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="module"></param>
|
||||||
|
/// <returns>A view model that allows selecting a value from the data model</returns>
|
||||||
DataModelDynamicViewModel GetDynamicSelectionViewModel(Module module);
|
DataModelDynamicViewModel GetDynamicSelectionViewModel(Module module);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a view model that allows entering a value matching the target data model type
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="targetType">The type of data model property to allow input for</param>
|
||||||
|
/// <param name="targetDescription">The description of the target data model property</param>
|
||||||
|
/// <returns>A view model that allows entering a value matching the target data model type</returns>
|
||||||
DataModelStaticViewModel GetStaticInputViewModel(Type targetType, DataModelPropertyAttribute targetDescription);
|
DataModelStaticViewModel GetStaticInputViewModel(Type targetType, DataModelPropertyAttribute targetDescription);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,14 +86,9 @@ namespace Artemis.UI
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UtilitiesOnShutdownRequested(object? sender, EventArgs e)
|
|
||||||
{
|
|
||||||
Execute.OnUIThread(() => Application.Current.Shutdown());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ConfigureIoC(IKernel kernel)
|
protected override void ConfigureIoC(IKernel kernel)
|
||||||
{
|
{
|
||||||
kernel.Settings.InjectNonPublic = true;
|
// kernel.Settings.InjectNonPublic = true;
|
||||||
|
|
||||||
// Load the UI modules
|
// Load the UI modules
|
||||||
kernel.Load<UIModule>();
|
kernel.Load<UIModule>();
|
||||||
@ -123,6 +118,11 @@ namespace Artemis.UI
|
|||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UtilitiesOnShutdownRequested(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Execute.OnUIThread(() => Application.Current.Shutdown());
|
||||||
|
}
|
||||||
|
|
||||||
private void CreateDataDirectory(ILogger logger)
|
private void CreateDataDirectory(ILogger logger)
|
||||||
{
|
{
|
||||||
// Ensure the data folder exists
|
// Ensure the data folder exists
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
LostFocus="{s:Action Submit}"
|
LostFocus="{s:Action Submit}"
|
||||||
Width="140">
|
Width="140">
|
||||||
<b:Interaction.Behaviors>
|
<b:Interaction.Behaviors>
|
||||||
<shared:PutCursorAtEndTextBoxBehavior />
|
<shared:PutCursorAtEndTextBox />
|
||||||
</b:Interaction.Behaviors>
|
</b:Interaction.Behaviors>
|
||||||
</TextBox>
|
</TextBox>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -16,7 +16,7 @@
|
|||||||
LostFocus="{s:Action Submit}"
|
LostFocus="{s:Action Submit}"
|
||||||
Width="140">
|
Width="140">
|
||||||
<b:Interaction.Behaviors>
|
<b:Interaction.Behaviors>
|
||||||
<shared:PutCursorAtEndTextBoxBehavior />
|
<shared:PutCursorAtEndTextBox />
|
||||||
</b:Interaction.Behaviors>
|
</b:Interaction.Behaviors>
|
||||||
</TextBox>
|
</TextBox>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -10,7 +10,7 @@
|
|||||||
d:DesignHeight="450" d:DesignWidth="800">
|
d:DesignHeight="450" d:DesignWidth="800">
|
||||||
<TextBox VerticalAlignment="Center" Text="{Binding InputValue}" LostFocus="{s:Action Submit}" Width="140">
|
<TextBox VerticalAlignment="Center" Text="{Binding InputValue}" LostFocus="{s:Action Submit}" Width="140">
|
||||||
<b:Interaction.Behaviors>
|
<b:Interaction.Behaviors>
|
||||||
<shared:PutCursorAtEndTextBoxBehavior />
|
<shared:PutCursorAtEndTextBox />
|
||||||
</b:Interaction.Behaviors>
|
</b:Interaction.Behaviors>
|
||||||
</TextBox>
|
</TextBox>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -4,6 +4,7 @@ using Artemis.UI.Stylet;
|
|||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Ninject.Extensions.Conventions;
|
using Ninject.Extensions.Conventions;
|
||||||
using Ninject.Modules;
|
using Ninject.Modules;
|
||||||
|
using Ninject.Planning.Bindings.Resolvers;
|
||||||
using Stylet;
|
using Stylet;
|
||||||
|
|
||||||
namespace Artemis.UI.Ninject
|
namespace Artemis.UI.Ninject
|
||||||
@ -19,7 +20,12 @@ namespace Artemis.UI.Ninject
|
|||||||
|
|
||||||
public override void Load()
|
public override void Load()
|
||||||
{
|
{
|
||||||
|
if (Kernel == null)
|
||||||
|
throw new ArgumentNullException("Kernel shouldn't be null here.");
|
||||||
|
|
||||||
|
Kernel.Components.Add<IMissingBindingResolver, UIElementSelfBindingResolver>();
|
||||||
Bind(typeof(IModelValidator<>)).To(typeof(FluentValidationAdapter<>));
|
Bind(typeof(IModelValidator<>)).To(typeof(FluentValidationAdapter<>));
|
||||||
|
|
||||||
Kernel.Bind(x =>
|
Kernel.Bind(x =>
|
||||||
{
|
{
|
||||||
x.From(Plugin.Assembly)
|
x.From(Plugin.Assembly)
|
||||||
|
|||||||
@ -3,6 +3,7 @@ using Artemis.UI.Ninject.Factories;
|
|||||||
using Artemis.UI.Ninject.InstanceProviders;
|
using Artemis.UI.Ninject.InstanceProviders;
|
||||||
using Artemis.UI.Screens;
|
using Artemis.UI.Screens;
|
||||||
using Artemis.UI.Screens.ProfileEditor;
|
using Artemis.UI.Screens.ProfileEditor;
|
||||||
|
using Artemis.UI.Screens.Splash;
|
||||||
using Artemis.UI.Services.Interfaces;
|
using Artemis.UI.Services.Interfaces;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using Artemis.UI.Stylet;
|
using Artemis.UI.Stylet;
|
||||||
@ -10,6 +11,7 @@ using FluentValidation;
|
|||||||
using Ninject.Extensions.Conventions;
|
using Ninject.Extensions.Conventions;
|
||||||
using Ninject.Extensions.Factory;
|
using Ninject.Extensions.Factory;
|
||||||
using Ninject.Modules;
|
using Ninject.Modules;
|
||||||
|
using Ninject.Planning.Bindings.Resolvers;
|
||||||
using Stylet;
|
using Stylet;
|
||||||
|
|
||||||
namespace Artemis.UI.Ninject
|
namespace Artemis.UI.Ninject
|
||||||
@ -21,6 +23,11 @@ namespace Artemis.UI.Ninject
|
|||||||
if (Kernel == null)
|
if (Kernel == null)
|
||||||
throw new ArgumentNullException("Kernel shouldn't be null here.");
|
throw new ArgumentNullException("Kernel shouldn't be null here.");
|
||||||
|
|
||||||
|
Kernel.Components.Add<IMissingBindingResolver, UIElementSelfBindingResolver>();
|
||||||
|
|
||||||
|
Kernel.Bind<TrayViewModel>().ToSelf().InSingletonScope();
|
||||||
|
Kernel.Bind<SplashViewModel>().ToSelf();
|
||||||
|
|
||||||
// Bind all built-in VMs
|
// Bind all built-in VMs
|
||||||
Kernel.Bind(x =>
|
Kernel.Bind(x =>
|
||||||
{
|
{
|
||||||
|
|||||||
75
src/Artemis.UI/Ninject/ViewsSelfBindingResolver.cs
Normal file
75
src/Artemis.UI/Ninject/ViewsSelfBindingResolver.cs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
using Ninject.Activation;
|
||||||
|
using Ninject.Activation.Providers;
|
||||||
|
using Ninject.Components;
|
||||||
|
using Ninject.Infrastructure;
|
||||||
|
using Ninject.Planning;
|
||||||
|
using Ninject.Planning.Bindings;
|
||||||
|
using Ninject.Planning.Bindings.Resolvers;
|
||||||
|
using Ninject.Selection.Heuristics;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Ninject
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a binding resolver that use the service in question itself as the target to activate but only if the service is a <see cref="UIElement"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class UIElementSelfBindingResolver : NinjectComponent, IMissingBindingResolver
|
||||||
|
{
|
||||||
|
private readonly IConstructorScorer constructorScorer;
|
||||||
|
private readonly IPlanner planner;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="UIElementSelfBindingResolver" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="planner">The <see cref="IPlanner" /> component.</param>
|
||||||
|
/// <param name="constructorScorer">The <see cref="IConstructorScorer" /> component.</param>
|
||||||
|
public UIElementSelfBindingResolver(IPlanner planner, IConstructorScorer constructorScorer)
|
||||||
|
{
|
||||||
|
this.planner = planner;
|
||||||
|
this.constructorScorer = constructorScorer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a value indicating whether the specified service is self-bindable.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service">The service.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <see langword="true" /> if the type is self-bindable; otherwise, <see langword="false" />.
|
||||||
|
/// </returns>
|
||||||
|
protected virtual bool TypeIsSelfBindable(Type service)
|
||||||
|
{
|
||||||
|
return !service.IsInterface
|
||||||
|
&& !service.IsAbstract
|
||||||
|
&& !service.IsValueType
|
||||||
|
&& service != typeof(string)
|
||||||
|
&& !service.ContainsGenericParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns any bindings from the specified collection that match the specified service.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bindings">The dictionary of all registered bindings.</param>
|
||||||
|
/// <param name="request">The service in question.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The series of matching bindings.
|
||||||
|
/// </returns>
|
||||||
|
public IEnumerable<IBinding> Resolve(Multimap<Type, IBinding> bindings, IRequest request)
|
||||||
|
{
|
||||||
|
Type service = request.Service;
|
||||||
|
|
||||||
|
if (!TypeIsSelfBindable(service) || service.IsAssignableFrom(typeof(UIElement)))
|
||||||
|
return Enumerable.Empty<IBinding>();
|
||||||
|
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
new Binding(service)
|
||||||
|
{
|
||||||
|
ProviderCallback = ctx => new StandardProvider(service, planner, constructorScorer)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,10 +8,11 @@
|
|||||||
xmlns:wpf="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:wpf="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
xmlns:modules="clr-namespace:Artemis.Core.Modules;assembly=Artemis.Core"
|
xmlns:modules="clr-namespace:Artemis.Core.Modules;assembly=Artemis.Core"
|
||||||
xmlns:dataModel="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
xmlns:dataModel="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||||
|
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:DataModelDebugViewModel}">
|
d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:DataModelDebugViewModel}">
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<dataModel:TypeToStringConverter x:Key="TypeToStringConverter"/>
|
<dataModel:TypeToStringConverter x:Key="TypeToStringConverter" />
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
@ -43,19 +44,11 @@
|
|||||||
wpf:HintAssist.Hint="Select a module"
|
wpf:HintAssist.Hint="Select a module"
|
||||||
IsEditable="True"
|
IsEditable="True"
|
||||||
TextSearch.TextPath="DisplayName"
|
TextSearch.TextPath="DisplayName"
|
||||||
|
DisplayMemberPath="DisplayName"
|
||||||
Margin="5 0 0 0"
|
Margin="5 0 0 0"
|
||||||
IsEnabled="{Binding IsModuleFilterEnabled}"
|
IsEnabled="{Binding IsModuleFilterEnabled}"
|
||||||
SelectedItem="{Binding SelectedModule}"
|
SelectedItem="{Binding SelectedModule}"
|
||||||
ItemsSource="{Binding Modules}">
|
ItemsSource="{Binding Modules}" />
|
||||||
<ComboBox.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<StackPanel d:DataContext="{d:DesignInstance Type=modules:Module}" Orientation="Horizontal">
|
|
||||||
<wpf:PackIcon Kind="{Binding DisplayIcon}" VerticalAlignment="Center" Margin="0 0 5 0" />
|
|
||||||
<TextBlock Text="{Binding DisplayName}" VerticalAlignment="Center" />
|
|
||||||
</StackPanel>
|
|
||||||
</DataTemplate>
|
|
||||||
</ComboBox.ItemTemplate>
|
|
||||||
</ComboBox>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<TreeView Grid.Row="1" ItemsSource="{Binding MainDataModel.Children}" HorizontalContentAlignment="Stretch">
|
<TreeView Grid.Row="1" ItemsSource="{Binding MainDataModel.Children}" HorizontalContentAlignment="Stretch">
|
||||||
<TreeView.Resources>
|
<TreeView.Resources>
|
||||||
@ -73,12 +66,12 @@
|
|||||||
<Run>[</Run><Run Text="{Binding DisplayValueType, Converter={StaticResource TypeToStringConverter}, Mode=OneWay}" /><Run>]</Run>
|
<Run>[</Run><Run Text="{Binding DisplayValueType, Converter={StaticResource TypeToStringConverter}, Mode=OneWay}" /><Run>]</Run>
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<TextBlock Grid.Column="1" Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" />
|
<TextBlock Grid.Column="1" Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" />
|
||||||
<TextBlock Grid.Column="2"
|
<TextBlock Grid.Column="2"
|
||||||
Text="{Binding DisplayValue}"
|
Text="{Binding DisplayValue}"
|
||||||
FontFamily="Consolas"
|
FontFamily="Consolas"
|
||||||
HorizontalAlignment="Right"/>
|
HorizontalAlignment="Right" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
</HierarchicalDataTemplate>
|
</HierarchicalDataTemplate>
|
||||||
<HierarchicalDataTemplate DataType="{x:Type dataModel:DataModelListViewModel}" ItemsSource="{Binding ListChildren}">
|
<HierarchicalDataTemplate DataType="{x:Type dataModel:DataModelListViewModel}" ItemsSource="{Binding ListChildren}">
|
||||||
<Grid>
|
<Grid>
|
||||||
|
|||||||
@ -28,6 +28,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
|
|||||||
_updateTimer = new Timer(500);
|
_updateTimer = new Timer(500);
|
||||||
|
|
||||||
DisplayName = "Data model";
|
DisplayName = "Data model";
|
||||||
|
Modules = new BindableCollection<Module>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DataModelPropertiesViewModel MainDataModel
|
public DataModelPropertiesViewModel MainDataModel
|
||||||
@ -42,11 +43,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
|
|||||||
set => SetAndNotify(ref _propertySearch, value);
|
set => SetAndNotify(ref _propertySearch, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Module> Modules
|
public BindableCollection<Module> Modules { get; }
|
||||||
{
|
|
||||||
get => _modules;
|
|
||||||
set => SetAndNotify(ref _modules, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Module SelectedModule
|
public Module SelectedModule
|
||||||
{
|
{
|
||||||
@ -77,8 +74,8 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
|
|||||||
GetDataModel();
|
GetDataModel();
|
||||||
_updateTimer.Start();
|
_updateTimer.Start();
|
||||||
_updateTimer.Elapsed += OnUpdateTimerOnElapsed;
|
_updateTimer.Elapsed += OnUpdateTimerOnElapsed;
|
||||||
_pluginManagementService.PluginEnabled += PluginManagementServiceOnPluginManagementToggled;
|
_pluginManagementService.PluginFeatureEnabled += OnPluginFeatureToggled;
|
||||||
_pluginManagementService.PluginDisabled += PluginManagementServiceOnPluginManagementToggled;
|
_pluginManagementService.PluginFeatureDisabled += OnPluginFeatureToggled;
|
||||||
|
|
||||||
PopulateModules();
|
PopulateModules();
|
||||||
|
|
||||||
@ -89,14 +86,23 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
|
|||||||
{
|
{
|
||||||
_updateTimer.Stop();
|
_updateTimer.Stop();
|
||||||
_updateTimer.Elapsed -= OnUpdateTimerOnElapsed;
|
_updateTimer.Elapsed -= OnUpdateTimerOnElapsed;
|
||||||
_pluginManagementService.PluginEnabled -= PluginManagementServiceOnPluginManagementToggled;
|
_pluginManagementService.PluginFeatureEnabled -= OnPluginFeatureToggled;
|
||||||
_pluginManagementService.PluginDisabled -= PluginManagementServiceOnPluginManagementToggled;
|
_pluginManagementService.PluginFeatureDisabled -= OnPluginFeatureToggled;
|
||||||
|
|
||||||
base.OnDeactivate();
|
base.OnDeactivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnPluginFeatureToggled(object sender, PluginFeatureEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.PluginFeature is DataModelPluginFeature)
|
||||||
|
PopulateModules();
|
||||||
|
}
|
||||||
|
|
||||||
private void OnUpdateTimerOnElapsed(object sender, ElapsedEventArgs args)
|
private void OnUpdateTimerOnElapsed(object sender, ElapsedEventArgs args)
|
||||||
{
|
{
|
||||||
|
if (MainDataModel == null)
|
||||||
|
return;
|
||||||
|
|
||||||
lock (MainDataModel)
|
lock (MainDataModel)
|
||||||
{
|
{
|
||||||
MainDataModel.Update(_dataModelUIService, null);
|
MainDataModel.Update(_dataModelUIService, null);
|
||||||
@ -110,14 +116,15 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
|
|||||||
: _dataModelUIService.GetMainDataModelVisualization();
|
: _dataModelUIService.GetMainDataModelVisualization();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PluginManagementServiceOnPluginManagementToggled(object? sender, PluginEventArgs e)
|
|
||||||
{
|
|
||||||
PopulateModules();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PopulateModules()
|
private void PopulateModules()
|
||||||
{
|
{
|
||||||
Modules = _pluginManagementService.GetFeaturesOfType<Module>().Where(p => p.IsEnabled).ToList();
|
Modules.Clear();
|
||||||
|
Modules.AddRange(_pluginManagementService.GetFeaturesOfType<Module>().Where(p => p.IsEnabled).OrderBy(m => m.DisplayName));
|
||||||
|
|
||||||
|
if (SelectedModule == null)
|
||||||
|
_dataModelUIService.UpdateModules(MainDataModel);
|
||||||
|
else if (!SelectedModule.IsEnabled)
|
||||||
|
SelectedModule = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -12,7 +12,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Modules
|
|||||||
private readonly IModuleService _moduleService;
|
private readonly IModuleService _moduleService;
|
||||||
private readonly IPluginManagementService _pluginManagementService;
|
private readonly IPluginManagementService _pluginManagementService;
|
||||||
private readonly DefaultDropHandler _defaultDropHandler;
|
private readonly DefaultDropHandler _defaultDropHandler;
|
||||||
private readonly List<ModuleOrderModuleViewModel> _modules;
|
private List<ModuleOrderModuleViewModel> _modules;
|
||||||
|
|
||||||
public ModuleOrderTabViewModel(IPluginManagementService pluginManagementService, IModuleService moduleService)
|
public ModuleOrderTabViewModel(IPluginManagementService pluginManagementService, IModuleService moduleService)
|
||||||
{
|
{
|
||||||
@ -20,16 +20,25 @@ namespace Artemis.UI.Screens.Settings.Tabs.Modules
|
|||||||
|
|
||||||
_pluginManagementService = pluginManagementService;
|
_pluginManagementService = pluginManagementService;
|
||||||
_moduleService = moduleService;
|
_moduleService = moduleService;
|
||||||
_modules = new List<ModuleOrderModuleViewModel>(pluginManagementService.GetFeaturesOfType<Module>().Select(m => new ModuleOrderModuleViewModel(m)));
|
|
||||||
_defaultDropHandler = new DefaultDropHandler();
|
_defaultDropHandler = new DefaultDropHandler();
|
||||||
|
|
||||||
NormalModules = new BindableCollection<ModuleOrderModuleViewModel>();
|
NormalModules = new BindableCollection<ModuleOrderModuleViewModel>();
|
||||||
ApplicationModules = new BindableCollection<ModuleOrderModuleViewModel>();
|
ApplicationModules = new BindableCollection<ModuleOrderModuleViewModel>();
|
||||||
OverlayModules = new BindableCollection<ModuleOrderModuleViewModel>();
|
OverlayModules = new BindableCollection<ModuleOrderModuleViewModel>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnActivate()
|
||||||
|
{
|
||||||
|
base.OnActivate();
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnDeactivate()
|
||||||
|
{
|
||||||
|
base.OnDeactivate();
|
||||||
|
_modules = null;
|
||||||
|
}
|
||||||
|
|
||||||
public BindableCollection<ModuleOrderModuleViewModel> NormalModules { get; set; }
|
public BindableCollection<ModuleOrderModuleViewModel> NormalModules { get; set; }
|
||||||
public BindableCollection<ModuleOrderModuleViewModel> ApplicationModules { get; set; }
|
public BindableCollection<ModuleOrderModuleViewModel> ApplicationModules { get; set; }
|
||||||
public BindableCollection<ModuleOrderModuleViewModel> OverlayModules { get; set; }
|
public BindableCollection<ModuleOrderModuleViewModel> OverlayModules { get; set; }
|
||||||
@ -67,6 +76,8 @@ namespace Artemis.UI.Screens.Settings.Tabs.Modules
|
|||||||
|
|
||||||
public void Update()
|
public void Update()
|
||||||
{
|
{
|
||||||
|
if (_modules == null)
|
||||||
|
_modules = _pluginManagementService.GetFeaturesOfType<Module>().Select(m => new ModuleOrderModuleViewModel(m)).ToList();
|
||||||
NormalModules.Clear();
|
NormalModules.Clear();
|
||||||
NormalModules.AddRange(_modules.Where(m => m.Module.PriorityCategory == ModulePriorityCategory.Normal).OrderBy(m => m.Module.Priority));
|
NormalModules.AddRange(_modules.Where(m => m.Module.PriorityCategory == ModulePriorityCategory.Normal).OrderBy(m => m.Module.Priority));
|
||||||
|
|
||||||
|
|||||||
@ -22,7 +22,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
|||||||
private readonly IPluginManagementService _pluginManagementService;
|
private readonly IPluginManagementService _pluginManagementService;
|
||||||
private readonly ISnackbarMessageQueue _snackbarMessageQueue;
|
private readonly ISnackbarMessageQueue _snackbarMessageQueue;
|
||||||
private bool _enabling;
|
private bool _enabling;
|
||||||
private bool _isEnabled;
|
|
||||||
|
|
||||||
public PluginFeatureViewModel(PluginFeature feature,
|
public PluginFeatureViewModel(PluginFeature feature,
|
||||||
IDialogService dialogService,
|
IDialogService dialogService,
|
||||||
@ -35,8 +34,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
|||||||
|
|
||||||
Feature = feature;
|
Feature = feature;
|
||||||
Icon = GetIconKind();
|
Icon = GetIconKind();
|
||||||
|
|
||||||
IsEnabled = Feature.IsEnabled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PluginFeature Feature { get; }
|
public PluginFeature Feature { get; }
|
||||||
@ -54,8 +51,8 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
|||||||
|
|
||||||
public bool IsEnabled
|
public bool IsEnabled
|
||||||
{
|
{
|
||||||
get => _isEnabled;
|
get => Feature.IsEnabled;
|
||||||
set => SetAndNotify(ref _isEnabled, value);
|
set => Task.Run(() => UpdateEnabled(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowLogsFolder()
|
public void ShowLogsFolder()
|
||||||
@ -100,7 +97,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.Run(() => _pluginManagementService.EnablePluginFeature(Feature));
|
await Task.Run(() => _pluginManagementService.EnablePluginFeature(Feature, true));
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -113,7 +110,8 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_pluginManagementService.DisablePluginFeature(Feature);
|
_pluginManagementService.DisablePluginFeature(Feature, true);
|
||||||
|
NotifyOfPropertyChange(nameof(IsEnabled));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,8 +148,8 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
|||||||
{
|
{
|
||||||
if (e.PluginFeature != Feature) return;
|
if (e.PluginFeature != Feature) return;
|
||||||
Enabling = false;
|
Enabling = false;
|
||||||
IsEnabled = e.PluginFeature.IsEnabled;
|
|
||||||
|
NotifyOfPropertyChange(nameof(IsEnabled));
|
||||||
NotifyOfPropertyChange(nameof(LoadException));
|
NotifyOfPropertyChange(nameof(LoadException));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,10 +9,30 @@
|
|||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignHeight="450" d:DesignWidth="800"
|
d:DesignHeight="450" d:DesignWidth="800"
|
||||||
d:DataContext="{d:DesignInstance local:PluginSettingsTabViewModel}">
|
d:DataContext="{d:DesignInstance local:PluginSettingsTabViewModel}">
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Margin="0 12 0 0">
|
<Grid>
|
||||||
<DockPanel Margin="15" MaxWidth="1230" HorizontalAlignment="Center">
|
<Grid.RowDefinitions>
|
||||||
<TextBlock DockPanel.Dock="Top">The list below shows all loaded plugins. If you're missing something, view your logs folder.</TextBlock>
|
<RowDefinition Height="Auto" />
|
||||||
<ItemsControl ItemsSource="{Binding Items}">
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid Margin="0 15" MaxWidth="800" >
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Grid.Column="0" TextWrapping="Wrap">
|
||||||
|
The list below shows all loaded plugins. If you're missing something, view your logs folder.
|
||||||
|
</TextBlock>
|
||||||
|
<materialDesign:PackIcon Grid.Column="1" Kind="Search" VerticalAlignment="Top" Margin="15 5 0 0" />
|
||||||
|
<TextBox Grid.Column="2"
|
||||||
|
materialDesign:TextFieldAssist.HasClearButton="True"
|
||||||
|
materialDesign:HintAssist.Hint="Search plugin"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Margin="5 0"
|
||||||
|
Text="{Binding SearchPluginInput, Delay=300, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
</Grid>
|
||||||
|
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||||
|
<ItemsControl ItemsSource="{Binding Items}" Margin="15 0 15 15" HorizontalAlignment="Center" MaxWidth="1700">
|
||||||
<ItemsControl.ItemsPanel>
|
<ItemsControl.ItemsPanel>
|
||||||
<ItemsPanelTemplate>
|
<ItemsPanelTemplate>
|
||||||
<WrapPanel />
|
<WrapPanel />
|
||||||
@ -24,6 +44,8 @@
|
|||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
</DockPanel>
|
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
@ -11,6 +12,8 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
|||||||
{
|
{
|
||||||
private readonly IPluginManagementService _pluginManagementService;
|
private readonly IPluginManagementService _pluginManagementService;
|
||||||
private readonly ISettingsVmFactory _settingsVmFactory;
|
private readonly ISettingsVmFactory _settingsVmFactory;
|
||||||
|
private string _searchPluginInput;
|
||||||
|
private List<PluginSettingsViewModel> _instances;
|
||||||
|
|
||||||
public PluginSettingsTabViewModel(IPluginManagementService pluginManagementService, ISettingsVmFactory settingsVmFactory)
|
public PluginSettingsTabViewModel(IPluginManagementService pluginManagementService, ISettingsVmFactory settingsVmFactory)
|
||||||
{
|
{
|
||||||
@ -20,17 +23,42 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
|||||||
_settingsVmFactory = settingsVmFactory;
|
_settingsVmFactory = settingsVmFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string SearchPluginInput
|
||||||
|
{
|
||||||
|
get => _searchPluginInput;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!SetAndNotify(ref _searchPluginInput, value)) return;
|
||||||
|
UpdatePluginSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdatePluginSearch()
|
||||||
|
{
|
||||||
|
if (_instances == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Items.Clear();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(SearchPluginInput))
|
||||||
|
Items.AddRange(_instances);
|
||||||
|
else
|
||||||
|
Items.AddRange(_instances.Where(i => i.Plugin.Info.Name.Contains(SearchPluginInput, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
i.Plugin.Info.Description.Contains(SearchPluginInput, StringComparison.OrdinalIgnoreCase)));
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnActivate()
|
protected override void OnActivate()
|
||||||
{
|
{
|
||||||
// Take it off the UI thread to avoid freezing on tab change
|
// Take it off the UI thread to avoid freezing on tab change
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
Items.Clear();
|
|
||||||
await Task.Delay(200);
|
await Task.Delay(200);
|
||||||
|
_instances = _pluginManagementService.GetAllPlugins()
|
||||||
|
.Select(p => _settingsVmFactory.CreatePluginSettingsViewModel(p))
|
||||||
|
.OrderBy(i => i.Plugin.Info.Name)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
List<PluginSettingsViewModel> instances = _pluginManagementService.GetAllPlugins().Select(p => _settingsVmFactory.CreatePluginSettingsViewModel(p)).ToList();
|
UpdatePluginSearch();
|
||||||
foreach (PluginSettingsViewModel pluginSettingsViewModel in instances.OrderBy(i => i.Plugin.Info.Name))
|
|
||||||
Items.Add(pluginSettingsViewModel);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
base.OnActivate();
|
base.OnActivate();
|
||||||
|
|||||||
@ -7,6 +7,8 @@
|
|||||||
xmlns:s="https://github.com/canton7/Stylet"
|
xmlns:s="https://github.com/canton7/Stylet"
|
||||||
xmlns:devices="clr-namespace:Artemis.UI.Screens.Settings.Tabs.Plugins"
|
xmlns:devices="clr-namespace:Artemis.UI.Screens.Settings.Tabs.Plugins"
|
||||||
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
|
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
|
||||||
|
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
|
||||||
|
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||||
d:DataContext="{d:DesignInstance devices:PluginSettingsViewModel}"
|
d:DataContext="{d:DesignInstance devices:PluginSettingsViewModel}"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignHeight="450" d:DesignWidth="800">
|
d:DesignHeight="450" d:DesignWidth="800">
|
||||||
@ -42,7 +44,7 @@
|
|||||||
VerticalAlignment="Top" />
|
VerticalAlignment="Top" />
|
||||||
|
|
||||||
<TextBlock Grid.Column="1" Grid.Row="0" Style="{StaticResource MaterialDesignBody2TextBlock}" Text="{Binding Plugin.Info.Name}" />
|
<TextBlock Grid.Column="1" Grid.Row="0" Style="{StaticResource MaterialDesignBody2TextBlock}" Text="{Binding Plugin.Info.Name}" />
|
||||||
|
|
||||||
<TextBlock Grid.Column="1"
|
<TextBlock Grid.Column="1"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
TextWrapping="Wrap"
|
TextWrapping="Wrap"
|
||||||
@ -51,37 +53,69 @@
|
|||||||
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" />
|
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<StackPanel Grid.Row="1" Grid.Column="0" Orientation="Horizontal">
|
|
||||||
<Button Style="{StaticResource MaterialDesignOutlinedButton}" ToolTip="MaterialDesignOutlinedButton" Margin="4" Command="{s:Action OpenSettings}">
|
<Button Grid.Row="1"
|
||||||
SETTINGS
|
Grid.Column="0"
|
||||||
</Button>
|
VerticalAlignment="Bottom"
|
||||||
</StackPanel>
|
Style="{StaticResource MaterialDesignOutlinedButton}"
|
||||||
<StackPanel Grid.Row="1" Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="8"
|
ToolTip="Open the plugins settings window"
|
||||||
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}">
|
Margin="4"
|
||||||
<CheckBox Style="{StaticResource MaterialDesignCheckBox}" IsChecked="{Binding IsEnabled}">
|
Command="{s:Action OpenSettings}">
|
||||||
Plugin enabled
|
SETTINGS
|
||||||
</CheckBox>
|
</Button>
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Grid.Row="1" Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="8"
|
<CheckBox Grid.Row="1"
|
||||||
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
|
Grid.Column="1"
|
||||||
<ProgressBar Style="{StaticResource MaterialDesignCircularProgressBar}" Value="0" IsIndeterminate="True" />
|
HorizontalAlignment="Right"
|
||||||
</StackPanel>
|
VerticalAlignment="Bottom"
|
||||||
|
Margin="8"
|
||||||
|
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}"
|
||||||
|
Style="{StaticResource MaterialDesignCheckBox}" IsChecked="{Binding IsEnabled}">
|
||||||
|
Plugin enabled
|
||||||
|
</CheckBox>
|
||||||
|
|
||||||
|
<ProgressBar Grid.Row="1"
|
||||||
|
Grid.Column="1"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Margin="8"
|
||||||
|
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"
|
||||||
|
Style="{StaticResource MaterialDesignCircularProgressBar}" Value="0"
|
||||||
|
IsIndeterminate="True" />
|
||||||
|
|
||||||
<Border Grid.Column="2" Grid.Row="0" Grid.RowSpan="2" BorderBrush="{StaticResource MaterialDesignDivider}" BorderThickness="1 0 0 0" Margin="10 0 0 0" Padding="10 0 0 0">
|
<Border Grid.Column="2" Grid.Row="0" Grid.RowSpan="2" BorderBrush="{StaticResource MaterialDesignDivider}" BorderThickness="1 0 0 0" Margin="10 0 0 0" Padding="10 0 0 0">
|
||||||
<StackPanel>
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
<TextBlock Margin="10 10 0 5" Style="{StaticResource MaterialDesignBody2TextBlock}">Plugin features</TextBlock>
|
<TextBlock Margin="10 10 0 5" Style="{StaticResource MaterialDesignBody2TextBlock}">Plugin features</TextBlock>
|
||||||
<ListBox ItemsSource="{Binding Items}"
|
<TextBlock Grid.Row="1"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Margin="0 30 0 0"
|
||||||
|
Style="{StaticResource MaterialDesignTextBlock}"
|
||||||
|
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
|
||||||
|
Visibility="{Binding IsEnabled, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}">
|
||||||
|
Enable the plugin to view its features
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<ListBox Grid.Row="1"
|
||||||
|
MaxHeight="135"
|
||||||
|
ItemsSource="{Binding Items}"
|
||||||
materialDesign:RippleAssist.IsDisabled="True"
|
materialDesign:RippleAssist.IsDisabled="True"
|
||||||
HorizontalContentAlignment="Stretch"
|
HorizontalContentAlignment="Stretch"
|
||||||
VirtualizingPanel.ScrollUnit="Pixel">
|
VirtualizingPanel.ScrollUnit="Pixel"
|
||||||
|
Visibility="{Binding IsEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
|
||||||
|
<b:Interaction.Behaviors>
|
||||||
|
<shared:ScrollParentWhenAtMax />
|
||||||
|
</b:Interaction.Behaviors>
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
|
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
</ListBox>
|
</ListBox>
|
||||||
</StackPanel>
|
</Grid>
|
||||||
|
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
</materialDesign:Card>
|
</materialDesign:Card>
|
||||||
|
|||||||
@ -126,7 +126,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.Run(() => _pluginManagementService.EnablePlugin(Plugin));
|
await Task.Run(() => _pluginManagementService.EnablePlugin(Plugin, true));
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -139,7 +139,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_pluginManagementService.DisablePlugin(Plugin);
|
_pluginManagementService.DisablePlugin(Plugin, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
NotifyOfPropertyChange(nameof(IsEnabled));
|
NotifyOfPropertyChange(nameof(IsEnabled));
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
using Artemis.Core;
|
using Artemis.Core.Services;
|
||||||
using Artemis.Core.Services;
|
|
||||||
using Artemis.UI.Events;
|
using Artemis.UI.Events;
|
||||||
using Artemis.UI.Screens.Splash;
|
using Artemis.UI.Screens.Splash;
|
||||||
using Artemis.UI.Services.Interfaces;
|
using Artemis.UI.Services.Interfaces;
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Artemis.Core;
|
using System.Linq;
|
||||||
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.DefaultTypes.DataModel.Display;
|
using Artemis.UI.DefaultTypes.DataModel.Display;
|
||||||
using Artemis.UI.DefaultTypes.DataModel.Input;
|
using Artemis.UI.DefaultTypes.DataModel.Input;
|
||||||
@ -80,7 +81,7 @@ namespace Artemis.UI.Services
|
|||||||
|
|
||||||
private void LoadPluginModules()
|
private void LoadPluginModules()
|
||||||
{
|
{
|
||||||
foreach (Plugin plugin in _pluginManagementService.GetAllPlugins())
|
foreach (Plugin plugin in _pluginManagementService.GetAllPlugins().Where(p => p.IsEnabled))
|
||||||
plugin.Kernel.Load(new[] {new PluginUIModule(plugin)});
|
plugin.Kernel.Load(new[] {new PluginUIModule(plugin)});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user