1
0
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:
Robert 2020-11-12 21:48:17 +01:00
parent e812929215
commit c146479393
38 changed files with 571 additions and 164 deletions

View File

@ -7,6 +7,7 @@ using LiteDB;
using Ninject.Activation;
using Ninject.Extensions.Conventions;
using Ninject.Modules;
using Ninject.Planning.Bindings.Resolvers;
using Serilog;
namespace Artemis.Core.Ninject
@ -22,6 +23,8 @@ namespace Artemis.Core.Ninject
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 =>
{
@ -88,6 +91,7 @@ namespace Artemis.Core.Ninject
Kernel.Bind<PluginSettings>().ToProvider<PluginSettingsProvider>();
Kernel.Bind<ILogger>().ToProvider<LoggerProvider>();
Kernel.Bind<LoggerProvider>().ToSelf();
}
private bool HasAccessToProtectedService(IRequest r)

View File

@ -1,19 +1,52 @@
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(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()
{
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());
});
}
}
}

View File

@ -13,7 +13,7 @@ namespace Artemis.Core.Ninject
private readonly IPluginRepository _pluginRepository;
private readonly IPluginManagementService _pluginManagementService;
internal PluginSettingsProvider(IPluginRepository pluginRepository, IPluginManagementService pluginManagementService)
public PluginSettingsProvider(IPluginRepository pluginRepository, IPluginManagementService pluginManagementService)
{
_pluginRepository = pluginRepository;
_pluginManagementService = pluginManagementService;

View File

@ -20,7 +20,7 @@ namespace Artemis.Core.DataModelExpansions
/// </summary>
public ReadOnlyCollection<PropertyInfo> HiddenProperties => HiddenPropertiesList.AsReadOnly();
internal DataModel InternalDataModel { get; set; }
internal DataModel? InternalDataModel { get; set; }
/// <summary>
/// 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>
public abstract void Update(double deltaTime);
internal void InternalUpdate(double deltaTime)
{
if (InternalDataModel != null)
Update(deltaTime);
}
/// <summary>
/// Override to provide your own data model description. By default this returns a description matching your plugin
/// name and description

View File

@ -129,14 +129,15 @@ namespace Artemis.Core
internal void RemoveFeature(PluginFeature feature)
{
if (feature.IsEnabled)
throw new ArtemisCoreException("Cannot remove an enabled feature from a plugin");
_features.Remove(feature);
feature.InternalDisable();
feature.Dispose();
OnFeatureRemoved(new PluginFeatureEventArgs(feature));
}
internal void SetEnabled(bool enable)
{
if (IsEnabled == enable)

View File

@ -87,8 +87,8 @@ namespace Artemis.Core
throw new ArtemisPluginLockException(LoadException);
}
IsEnabled = true;
CreateLockFile();
IsEnabled = true;
// 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

View File

@ -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
foreach (BaseDataModelExpansion dataModelExpansion in _dataModelExpansions.Where(e => e.IsEnabled))
dataModelExpansion.Update(args.DeltaTime);
dataModelExpansion.InternalUpdate(args.DeltaTime);
}
List<Module> modules;

View File

@ -43,7 +43,8 @@ namespace Artemis.Core.Services
/// Enables the provided <paramref name="plugin" />
/// </summary>
/// <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>
/// Unloads the provided <paramref name="plugin" />
@ -55,20 +56,23 @@ namespace Artemis.Core.Services
/// Disables the provided <paramref name="plugin" />
/// </summary>
/// <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>
/// Enables the provided plugin feature
/// </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>
void EnablePluginFeature(PluginFeature pluginFeature, bool isAutoEnable = false);
void EnablePluginFeature(PluginFeature pluginFeature, bool saveState, bool isAutoEnable = false);
/// <summary>
/// Disables the provided plugin feature
/// </summary>
/// <param name="pluginFeature"></param>
void DisablePluginFeature(PluginFeature pluginFeature);
/// <param name="pluginFeature">The feature to enable</param>
/// <param name="saveState">Whether or not to save the new enabled state</param>
void DisablePluginFeature(PluginFeature pluginFeature, bool saveState);
/// <summary>
/// Gets the plugin info of all loaded plugins

View 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
{
}
}

View File

@ -185,7 +185,8 @@ namespace Artemis.Core.Services
try
{
Plugin plugin = LoadPlugin(subDirectory);
EnablePlugin(plugin, ignorePluginLock);
if (plugin.Entity.IsEnabled)
EnablePlugin(plugin, false, ignorePluginLock);
}
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)
throw new ArtemisPluginException(plugin, "Cannot enable a plugin that hasn't successfully been loaded");
// Create the Ninject child kernel and load the module
plugin.Kernel = new ChildKernel(_kernel);
plugin.Kernel.Bind<Plugin>().ToConstant(plugin);
plugin.Kernel.Load(new PluginModule(plugin.Info));
plugin.Kernel = new ChildKernel(_kernel, new PluginModule(plugin));
plugin.SetEnabled(true);
// 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
{
plugin.Kernel.Bind(featureType).ToSelf().InSingletonScope();
// Include Plugin as a parameter for the PluginSettingsProvider
IParameter[] parameters = {new Parameter("Plugin", plugin, false)};
PluginFeature instance = (PluginFeature) plugin.Kernel.Get(featureType, parameters);
@ -314,7 +314,7 @@ namespace Artemis.Core.Services
}
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
{
EnablePluginFeature(pluginFeature, !ignorePluginLock);
EnablePluginFeature(pluginFeature, false, !ignorePluginLock);
}
catch (Exception)
{
@ -331,6 +331,12 @@ namespace Artemis.Core.Services
}
}
if (saveState)
{
plugin.Entity.IsEnabled = plugin.IsEnabled;
SavePlugin(plugin);
}
OnPluginEnabled(new PluginEventArgs(plugin));
}
@ -340,7 +346,7 @@ namespace Artemis.Core.Services
{
try
{
DisablePlugin(plugin);
DisablePlugin(plugin, false);
}
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)
return;
@ -364,14 +370,25 @@ namespace Artemis.Core.Services
while (plugin.Features.Any())
{
PluginFeature feature = plugin.Features[0];
if (feature.IsEnabled)
DisablePluginFeature(feature, false);
plugin.RemoveFeature(feature);
OnPluginFeatureDisabled(new PluginFeatureEventArgs(feature));
}
plugin.SetEnabled(false);
plugin.Kernel.Dispose();
plugin.Kernel = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
if (saveState)
{
plugin.Entity.IsEnabled = plugin.IsEnabled;
SavePlugin(plugin);
}
OnPluginDisabled(new PluginEventArgs(plugin));
}
@ -380,7 +397,7 @@ namespace Artemis.Core.Services
#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);
@ -389,16 +406,9 @@ namespace Artemis.Core.Services
OnPluginFeatureEnabling(new PluginFeatureEventArgs(pluginFeature));
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.Entity.IsEnabled = true;
if (saveState)
pluginFeature.Entity.IsEnabled = true;
}
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
// not affect the user's settings
if (isAutoEnable)
pluginFeature.Entity.IsEnabled = true;
if (saveState)
{
if (isAutoEnable)
pluginFeature.Entity.IsEnabled = true;
SavePlugin(pluginFeature.Plugin);
SavePlugin(pluginFeature.Plugin);
}
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)
{
try
{
_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);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
finally
{
pluginFeature.Entity.IsEnabled = false;
SavePlugin(pluginFeature.Plugin);
if (saveState)
{
pluginFeature.Entity.IsEnabled = false;
SavePlugin(pluginFeature.Plugin);
}
if (!pluginFeature.IsEnabled)
{

View File

@ -7,7 +7,7 @@ namespace Artemis.Core.Services
{
private readonly PluginSettings _pluginSettings;
internal SettingsService(IPluginRepository pluginRepository)
public SettingsService(IPluginRepository pluginRepository)
{
_pluginSettings = new PluginSettings(Constants.CorePlugin, pluginRepository);
}

View File

@ -17,7 +17,7 @@ namespace Artemis.Core.Services
private readonly IProfileRepository _profileRepository;
private readonly ISurfaceService _surfaceService;
internal ProfileService(ILogger logger,
public ProfileService(ILogger logger,
IPluginManagementService pluginManagementService,
ISurfaceService surfaceService,
IConditionOperatorService conditionOperatorService,

View File

@ -6,11 +6,11 @@ using LiteDB;
namespace Artemis.Storage.Repositories
{
public class ModuleRepository : IModuleRepository
internal class ModuleRepository : IModuleRepository
{
private readonly LiteRepository _repository;
internal ModuleRepository(LiteRepository repository)
public ModuleRepository(LiteRepository repository)
{
_repository = repository;
_repository.Database.GetCollection<ModuleSettingsEntity>().EnsureIndex(s => s.ModuleId, true);

View File

@ -5,11 +5,11 @@ using LiteDB;
namespace Artemis.Storage.Repositories
{
public class PluginRepository : IPluginRepository
internal class PluginRepository : IPluginRepository
{
private readonly LiteRepository _repository;
internal PluginRepository(LiteRepository repository)
public PluginRepository(LiteRepository repository)
{
_repository = repository;

View File

@ -6,11 +6,11 @@ using LiteDB;
namespace Artemis.Storage.Repositories
{
public class ProfileRepository : IProfileRepository
internal class ProfileRepository : IProfileRepository
{
private readonly LiteRepository _repository;
internal ProfileRepository(LiteRepository repository)
public ProfileRepository(LiteRepository repository)
{
_repository = repository;
_repository.Database.GetCollection<ProfileEntity>().EnsureIndex(s => s.Name);

View File

@ -5,11 +5,11 @@ using LiteDB;
namespace Artemis.Storage.Repositories
{
public class SurfaceRepository : ISurfaceRepository
internal class SurfaceRepository : ISurfaceRepository
{
private readonly LiteRepository _repository;
internal SurfaceRepository(LiteRepository repository)
public SurfaceRepository(LiteRepository repository)
{
_repository = repository;
_repository.Database.GetCollection<SurfaceEntity>().EnsureIndex(s => s.Name);

View File

@ -4,7 +4,7 @@ using Microsoft.Xaml.Behaviors;
namespace Artemis.UI.Shared
{
public class PutCursorAtEndTextBoxBehavior : Behavior<UIElement>
public class PutCursorAtEndTextBox : Behavior<UIElement>
{
private TextBox _textBox;

View 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;
}
}
}

View File

@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
using System.Windows.Media;
using Artemis.Core;
using SkiaSharp;
using Stylet;
namespace Artemis.UI.Shared
{
@ -14,13 +14,13 @@ namespace Artemis.UI.Shared
/// Converts <see cref="T:Artemis.Core.Models.Profile.ColorGradient" /> into a
/// <see cref="T:System.Windows.Media.GradientStopCollection" />.
/// </summary>
[ValueConversion(typeof(BindableCollection<ColorGradientStop>), typeof(GradientStopCollection))]
[ValueConversion(typeof(List<ColorGradientStop>), typeof(GradientStopCollection))]
public class ColorGradientToGradientStopsConverter : IValueConverter
{
/// <inheritdoc />
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();
if (colorGradients == null)
return collection;
@ -34,7 +34,7 @@ namespace Artemis.UI.Shared
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
GradientStopCollection collection = (GradientStopCollection) value;
BindableCollection<ColorGradientStop> colorGradients = new BindableCollection<ColorGradientStop>();
List<ColorGradientStop> colorGradients = new List<ColorGradientStop>();
if (collection == null)
return colorGradients;

View File

@ -33,7 +33,7 @@ namespace Artemis.UI.Shared.Services
public DataModelPropertiesViewModel GetMainDataModelVisualization()
{
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)));
// Update to populate children
@ -42,6 +42,25 @@ namespace Artemis.UI.Shared.Services
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)
{
if (includeMainDataModel)
@ -64,7 +83,7 @@ namespace Artemis.UI.Shared.Services
return 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
viewModel.Update(this, null);

View File

@ -7,22 +7,114 @@ using Artemis.UI.Shared.Input;
namespace Artemis.UI.Shared.Services
{
/// <summary>
/// A service for UI related data model tasks
/// </summary>
public interface IDataModelUIService : IArtemisSharedUIService
{
/// <summary>
/// Gets a read-only list of all registered data model editors
/// </summary>
IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelEditors { get; }
/// <summary>
/// Gets a read-only list of all registered data model displays
/// </summary>
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();
/// <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);
/// <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;
/// <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;
/// <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);
/// <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);
/// <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);
/// <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);
/// <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);
/// <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);
}
}

View File

@ -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)
{
kernel.Settings.InjectNonPublic = true;
// kernel.Settings.InjectNonPublic = true;
// Load the UI modules
kernel.Load<UIModule>();
@ -123,6 +118,11 @@ namespace Artemis.UI
e.Handled = true;
}
private void UtilitiesOnShutdownRequested(object? sender, EventArgs e)
{
Execute.OnUIThread(() => Application.Current.Shutdown());
}
private void CreateDataDirectory(ILogger logger)
{
// Ensure the data folder exists

View File

@ -16,7 +16,7 @@
LostFocus="{s:Action Submit}"
Width="140">
<b:Interaction.Behaviors>
<shared:PutCursorAtEndTextBoxBehavior />
<shared:PutCursorAtEndTextBox />
</b:Interaction.Behaviors>
</TextBox>
</UserControl>

View File

@ -16,7 +16,7 @@
LostFocus="{s:Action Submit}"
Width="140">
<b:Interaction.Behaviors>
<shared:PutCursorAtEndTextBoxBehavior />
<shared:PutCursorAtEndTextBox />
</b:Interaction.Behaviors>
</TextBox>
</UserControl>

View File

@ -10,7 +10,7 @@
d:DesignHeight="450" d:DesignWidth="800">
<TextBox VerticalAlignment="Center" Text="{Binding InputValue}" LostFocus="{s:Action Submit}" Width="140">
<b:Interaction.Behaviors>
<shared:PutCursorAtEndTextBoxBehavior />
<shared:PutCursorAtEndTextBox />
</b:Interaction.Behaviors>
</TextBox>
</UserControl>

View File

@ -4,6 +4,7 @@ using Artemis.UI.Stylet;
using FluentValidation;
using Ninject.Extensions.Conventions;
using Ninject.Modules;
using Ninject.Planning.Bindings.Resolvers;
using Stylet;
namespace Artemis.UI.Ninject
@ -19,7 +20,12 @@ namespace Artemis.UI.Ninject
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<>));
Kernel.Bind(x =>
{
x.From(Plugin.Assembly)

View File

@ -3,6 +3,7 @@ using Artemis.UI.Ninject.Factories;
using Artemis.UI.Ninject.InstanceProviders;
using Artemis.UI.Screens;
using Artemis.UI.Screens.ProfileEditor;
using Artemis.UI.Screens.Splash;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared.Services;
using Artemis.UI.Stylet;
@ -10,6 +11,7 @@ using FluentValidation;
using Ninject.Extensions.Conventions;
using Ninject.Extensions.Factory;
using Ninject.Modules;
using Ninject.Planning.Bindings.Resolvers;
using Stylet;
namespace Artemis.UI.Ninject
@ -21,6 +23,11 @@ namespace Artemis.UI.Ninject
if (Kernel == null)
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
Kernel.Bind(x =>
{

View 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)
}
};
}
}
}

View File

@ -8,10 +8,11 @@
xmlns:wpf="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:modules="clr-namespace:Artemis.Core.Modules;assembly=Artemis.Core"
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"
d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:DataModelDebugViewModel}">
<UserControl.Resources>
<dataModel:TypeToStringConverter x:Key="TypeToStringConverter"/>
<dataModel:TypeToStringConverter x:Key="TypeToStringConverter" />
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
@ -43,19 +44,11 @@
wpf:HintAssist.Hint="Select a module"
IsEditable="True"
TextSearch.TextPath="DisplayName"
DisplayMemberPath="DisplayName"
Margin="5 0 0 0"
IsEnabled="{Binding IsModuleFilterEnabled}"
SelectedItem="{Binding SelectedModule}"
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>
ItemsSource="{Binding Modules}" />
</Grid>
<TreeView Grid.Row="1" ItemsSource="{Binding MainDataModel.Children}" HorizontalContentAlignment="Stretch">
<TreeView.Resources>
@ -73,12 +66,12 @@
<Run>[</Run><Run Text="{Binding DisplayValueType, Converter={StaticResource TypeToStringConverter}, Mode=OneWay}" /><Run>]</Run>
</TextBlock>
<TextBlock Grid.Column="1" Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" />
<TextBlock Grid.Column="2"
Text="{Binding DisplayValue}"
FontFamily="Consolas"
HorizontalAlignment="Right"/>
<TextBlock Grid.Column="2"
Text="{Binding DisplayValue}"
FontFamily="Consolas"
HorizontalAlignment="Right" />
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type dataModel:DataModelListViewModel}" ItemsSource="{Binding ListChildren}">
<Grid>

View File

@ -28,6 +28,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
_updateTimer = new Timer(500);
DisplayName = "Data model";
Modules = new BindableCollection<Module>();
}
public DataModelPropertiesViewModel MainDataModel
@ -42,11 +43,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
set => SetAndNotify(ref _propertySearch, value);
}
public List<Module> Modules
{
get => _modules;
set => SetAndNotify(ref _modules, value);
}
public BindableCollection<Module> Modules { get; }
public Module SelectedModule
{
@ -77,8 +74,8 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
GetDataModel();
_updateTimer.Start();
_updateTimer.Elapsed += OnUpdateTimerOnElapsed;
_pluginManagementService.PluginEnabled += PluginManagementServiceOnPluginManagementToggled;
_pluginManagementService.PluginDisabled += PluginManagementServiceOnPluginManagementToggled;
_pluginManagementService.PluginFeatureEnabled += OnPluginFeatureToggled;
_pluginManagementService.PluginFeatureDisabled += OnPluginFeatureToggled;
PopulateModules();
@ -89,14 +86,23 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
{
_updateTimer.Stop();
_updateTimer.Elapsed -= OnUpdateTimerOnElapsed;
_pluginManagementService.PluginEnabled -= PluginManagementServiceOnPluginManagementToggled;
_pluginManagementService.PluginDisabled -= PluginManagementServiceOnPluginManagementToggled;
_pluginManagementService.PluginFeatureEnabled -= OnPluginFeatureToggled;
_pluginManagementService.PluginFeatureDisabled -= OnPluginFeatureToggled;
base.OnDeactivate();
}
private void OnPluginFeatureToggled(object sender, PluginFeatureEventArgs e)
{
if (e.PluginFeature is DataModelPluginFeature)
PopulateModules();
}
private void OnUpdateTimerOnElapsed(object sender, ElapsedEventArgs args)
{
if (MainDataModel == null)
return;
lock (MainDataModel)
{
MainDataModel.Update(_dataModelUIService, null);
@ -110,14 +116,15 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
: _dataModelUIService.GetMainDataModelVisualization();
}
private void PluginManagementServiceOnPluginManagementToggled(object? sender, PluginEventArgs e)
{
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;
}
}
}

View File

@ -12,7 +12,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Modules
private readonly IModuleService _moduleService;
private readonly IPluginManagementService _pluginManagementService;
private readonly DefaultDropHandler _defaultDropHandler;
private readonly List<ModuleOrderModuleViewModel> _modules;
private List<ModuleOrderModuleViewModel> _modules;
public ModuleOrderTabViewModel(IPluginManagementService pluginManagementService, IModuleService moduleService)
{
@ -20,16 +20,25 @@ namespace Artemis.UI.Screens.Settings.Tabs.Modules
_pluginManagementService = pluginManagementService;
_moduleService = moduleService;
_modules = new List<ModuleOrderModuleViewModel>(pluginManagementService.GetFeaturesOfType<Module>().Select(m => new ModuleOrderModuleViewModel(m)));
_defaultDropHandler = new DefaultDropHandler();
NormalModules = new BindableCollection<ModuleOrderModuleViewModel>();
ApplicationModules = new BindableCollection<ModuleOrderModuleViewModel>();
OverlayModules = new BindableCollection<ModuleOrderModuleViewModel>();
}
protected override void OnActivate()
{
base.OnActivate();
Update();
}
protected override void OnDeactivate()
{
base.OnDeactivate();
_modules = null;
}
public BindableCollection<ModuleOrderModuleViewModel> NormalModules { get; set; }
public BindableCollection<ModuleOrderModuleViewModel> ApplicationModules { get; set; }
public BindableCollection<ModuleOrderModuleViewModel> OverlayModules { get; set; }
@ -67,6 +76,8 @@ namespace Artemis.UI.Screens.Settings.Tabs.Modules
public void Update()
{
if (_modules == null)
_modules = _pluginManagementService.GetFeaturesOfType<Module>().Select(m => new ModuleOrderModuleViewModel(m)).ToList();
NormalModules.Clear();
NormalModules.AddRange(_modules.Where(m => m.Module.PriorityCategory == ModulePriorityCategory.Normal).OrderBy(m => m.Module.Priority));

View File

@ -22,7 +22,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
private readonly IPluginManagementService _pluginManagementService;
private readonly ISnackbarMessageQueue _snackbarMessageQueue;
private bool _enabling;
private bool _isEnabled;
public PluginFeatureViewModel(PluginFeature feature,
IDialogService dialogService,
@ -35,8 +34,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
Feature = feature;
Icon = GetIconKind();
IsEnabled = Feature.IsEnabled;
}
public PluginFeature Feature { get; }
@ -54,8 +51,8 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
public bool IsEnabled
{
get => _isEnabled;
set => SetAndNotify(ref _isEnabled, value);
get => Feature.IsEnabled;
set => Task.Run(() => UpdateEnabled(value));
}
public void ShowLogsFolder()
@ -100,7 +97,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
try
{
await Task.Run(() => _pluginManagementService.EnablePluginFeature(Feature));
await Task.Run(() => _pluginManagementService.EnablePluginFeature(Feature, true));
}
catch (Exception e)
{
@ -113,7 +110,8 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
}
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;
Enabling = false;
IsEnabled = e.PluginFeature.IsEnabled;
NotifyOfPropertyChange(nameof(IsEnabled));
NotifyOfPropertyChange(nameof(LoadException));
}

View File

@ -9,10 +9,30 @@
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:PluginSettingsTabViewModel}">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Margin="0 12 0 0">
<DockPanel Margin="15" MaxWidth="1230" HorizontalAlignment="Center">
<TextBlock DockPanel.Dock="Top">The list below shows all loaded plugins. If you're missing something, view your logs folder.</TextBlock>
<ItemsControl ItemsSource="{Binding Items}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<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>
<ItemsPanelTemplate>
<WrapPanel />
@ -24,6 +44,8 @@
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
</ScrollViewer>
</ScrollViewer>
</Grid>
</UserControl>

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core.Services;
@ -11,6 +12,8 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
{
private readonly IPluginManagementService _pluginManagementService;
private readonly ISettingsVmFactory _settingsVmFactory;
private string _searchPluginInput;
private List<PluginSettingsViewModel> _instances;
public PluginSettingsTabViewModel(IPluginManagementService pluginManagementService, ISettingsVmFactory settingsVmFactory)
{
@ -20,17 +23,42 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
_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()
{
// Take it off the UI thread to avoid freezing on tab change
Task.Run(async () =>
{
Items.Clear();
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();
foreach (PluginSettingsViewModel pluginSettingsViewModel in instances.OrderBy(i => i.Plugin.Info.Name))
Items.Add(pluginSettingsViewModel);
UpdatePluginSearch();
});
base.OnActivate();

View File

@ -7,6 +7,8 @@
xmlns:s="https://github.com/canton7/Stylet"
xmlns:devices="clr-namespace:Artemis.UI.Screens.Settings.Tabs.Plugins"
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}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
@ -42,7 +44,7 @@
VerticalAlignment="Top" />
<TextBlock Grid.Column="1" Grid.Row="0" Style="{StaticResource MaterialDesignBody2TextBlock}" Text="{Binding Plugin.Info.Name}" />
<TextBlock Grid.Column="1"
Grid.Row="1"
TextWrapping="Wrap"
@ -51,37 +53,69 @@
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" />
</Grid>
<StackPanel Grid.Row="1" Grid.Column="0" Orientation="Horizontal">
<Button Style="{StaticResource MaterialDesignOutlinedButton}" ToolTip="MaterialDesignOutlinedButton" Margin="4" Command="{s:Action OpenSettings}">
SETTINGS
</Button>
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="8"
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}">
<CheckBox Style="{StaticResource MaterialDesignCheckBox}" IsChecked="{Binding IsEnabled}">
Plugin enabled
</CheckBox>
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="8"
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
<ProgressBar Style="{StaticResource MaterialDesignCircularProgressBar}" Value="0" IsIndeterminate="True" />
</StackPanel>
<Button Grid.Row="1"
Grid.Column="0"
VerticalAlignment="Bottom"
Style="{StaticResource MaterialDesignOutlinedButton}"
ToolTip="Open the plugins settings window"
Margin="4"
Command="{s:Action OpenSettings}">
SETTINGS
</Button>
<CheckBox Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Right"
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">
<StackPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<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"
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>
<DataTemplate>
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
</Border>
</Grid>
</materialDesign:Card>

View File

@ -126,7 +126,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
try
{
await Task.Run(() => _pluginManagementService.EnablePlugin(Plugin));
await Task.Run(() => _pluginManagementService.EnablePlugin(Plugin, true));
}
catch (Exception e)
{
@ -139,7 +139,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
}
else
{
_pluginManagementService.DisablePlugin(Plugin);
_pluginManagementService.DisablePlugin(Plugin, true);
}
NotifyOfPropertyChange(nameof(IsEnabled));

View File

@ -1,5 +1,4 @@
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.Core.Services;
using Artemis.UI.Events;
using Artemis.UI.Screens.Splash;
using Artemis.UI.Services.Interfaces;

View File

@ -1,4 +1,5 @@
using Artemis.Core;
using System.Linq;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.DefaultTypes.DataModel.Display;
using Artemis.UI.DefaultTypes.DataModel.Input;
@ -80,7 +81,7 @@ namespace Artemis.UI.Services
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)});
}
}