diff --git a/src/Artemis.Core/Ninject/CoreModule.cs b/src/Artemis.Core/Ninject/CoreModule.cs index 28fe3bcf0..5408951de 100644 --- a/src/Artemis.Core/Ninject/CoreModule.cs +++ b/src/Artemis.Core/Ninject/CoreModule.cs @@ -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(); + // Bind all services as singletons Kernel.Bind(x => { @@ -88,6 +91,7 @@ namespace Artemis.Core.Ninject Kernel.Bind().ToProvider(); Kernel.Bind().ToProvider(); + Kernel.Bind().ToSelf(); } private bool HasAccessToProtectedService(IRequest r) diff --git a/src/Artemis.Core/Ninject/PluginModule.cs b/src/Artemis.Core/Ninject/PluginModule.cs index bd1b81ddd..f6909993b 100644 --- a/src/Artemis.Core/Ninject/PluginModule.cs +++ b/src/Artemis.Core/Ninject/PluginModule.cs @@ -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(); + + Kernel.Bind().ToConstant(Plugin); + + // Bind plugin service interfaces + Kernel.Bind(x => + { + x.From(Plugin.Assembly) + .IncludingNonPublicTypes() + .SelectAllClasses() + .InheritedFrom() + .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() + .BindToSelf() + .Configure(c => c.InSingletonScope()); + }); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Ninject/PluginSettingsProvider.cs b/src/Artemis.Core/Ninject/PluginSettingsProvider.cs index e2f303ce7..66b707460 100644 --- a/src/Artemis.Core/Ninject/PluginSettingsProvider.cs +++ b/src/Artemis.Core/Ninject/PluginSettingsProvider.cs @@ -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; diff --git a/src/Artemis.Core/Plugins/DataModelExpansions/Internal/BaseDataModelExpansion.cs b/src/Artemis.Core/Plugins/DataModelExpansions/Internal/BaseDataModelExpansion.cs index 9ab4f034f..d11c81bd7 100644 --- a/src/Artemis.Core/Plugins/DataModelExpansions/Internal/BaseDataModelExpansion.cs +++ b/src/Artemis.Core/Plugins/DataModelExpansions/Internal/BaseDataModelExpansion.cs @@ -20,7 +20,7 @@ namespace Artemis.Core.DataModelExpansions /// public ReadOnlyCollection HiddenProperties => HiddenPropertiesList.AsReadOnly(); - internal DataModel InternalDataModel { get; set; } + internal DataModel? InternalDataModel { get; set; } /// /// Called each frame when the data model should update @@ -28,6 +28,12 @@ namespace Artemis.Core.DataModelExpansions /// Time in seconds since the last update public abstract void Update(double deltaTime); + internal void InternalUpdate(double deltaTime) + { + if (InternalDataModel != null) + Update(deltaTime); + } + /// /// Override to provide your own data model description. By default this returns a description matching your plugin /// name and description diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs index 481fa80f0..1182e8920 100644 --- a/src/Artemis.Core/Plugins/Plugin.cs +++ b/src/Artemis.Core/Plugins/Plugin.cs @@ -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) diff --git a/src/Artemis.Core/Plugins/PluginFeature.cs b/src/Artemis.Core/Plugins/PluginFeature.cs index 0b3f4a152..06f09b90b 100644 --- a/src/Artemis.Core/Plugins/PluginFeature.cs +++ b/src/Artemis.Core/Plugins/PluginFeature.cs @@ -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 diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 4bb6ce3e3..a3ab25712 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -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 modules; diff --git a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs index 233da594f..c85eba6b3 100644 --- a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs +++ b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs @@ -43,7 +43,8 @@ namespace Artemis.Core.Services /// Enables the provided /// /// The plugin to enable - void EnablePlugin(Plugin plugin, bool ignorePluginLock = false); + /// Whether or not to save the new enabled state + void EnablePlugin(Plugin plugin, bool saveState, bool ignorePluginLock = false); /// /// Unloads the provided @@ -55,20 +56,23 @@ namespace Artemis.Core.Services /// Disables the provided /// /// The plugin to disable - void DisablePlugin(Plugin plugin); + /// Whether or not to save the new enabled state + void DisablePlugin(Plugin plugin, bool saveState); /// /// Enables the provided plugin feature /// - /// + /// The feature to enable + /// Whether or not to save the new enabled state /// If true, fails if there is a lock file present - void EnablePluginFeature(PluginFeature pluginFeature, bool isAutoEnable = false); + void EnablePluginFeature(PluginFeature pluginFeature, bool saveState, bool isAutoEnable = false); /// /// Disables the provided plugin feature /// - /// - void DisablePluginFeature(PluginFeature pluginFeature); + /// The feature to enable + /// Whether or not to save the new enabled state + void DisablePluginFeature(PluginFeature pluginFeature, bool saveState); /// /// Gets the plugin info of all loaded plugins diff --git a/src/Artemis.Core/Services/Interfaces/IPluginService.cs b/src/Artemis.Core/Services/Interfaces/IPluginService.cs new file mode 100644 index 000000000..4ac1ced7c --- /dev/null +++ b/src/Artemis.Core/Services/Interfaces/IPluginService.cs @@ -0,0 +1,13 @@ +namespace Artemis.Core.Services +{ + /// + /// An interface for services provided by plugins. + /// + /// Any service implementing this interface will be available inside the plugin as a singleton through dependency + /// injection + /// + /// + public interface IPluginService + { + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index d64ad5d41..fdf0869a1 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -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().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) { diff --git a/src/Artemis.Core/Services/SettingsService.cs b/src/Artemis.Core/Services/SettingsService.cs index b00f5cee4..a5ca7d658 100644 --- a/src/Artemis.Core/Services/SettingsService.cs +++ b/src/Artemis.Core/Services/SettingsService.cs @@ -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); } diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index e142952d5..6ff5a49fd 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -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, diff --git a/src/Artemis.Storage/Repositories/ModuleRepository.cs b/src/Artemis.Storage/Repositories/ModuleRepository.cs index ff9d4953b..9d733507c 100644 --- a/src/Artemis.Storage/Repositories/ModuleRepository.cs +++ b/src/Artemis.Storage/Repositories/ModuleRepository.cs @@ -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().EnsureIndex(s => s.ModuleId, true); diff --git a/src/Artemis.Storage/Repositories/PluginRepository.cs b/src/Artemis.Storage/Repositories/PluginRepository.cs index 7d0f79652..b97c8fc83 100644 --- a/src/Artemis.Storage/Repositories/PluginRepository.cs +++ b/src/Artemis.Storage/Repositories/PluginRepository.cs @@ -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; diff --git a/src/Artemis.Storage/Repositories/ProfileRepository.cs b/src/Artemis.Storage/Repositories/ProfileRepository.cs index 288b40fcb..1fdc88868 100644 --- a/src/Artemis.Storage/Repositories/ProfileRepository.cs +++ b/src/Artemis.Storage/Repositories/ProfileRepository.cs @@ -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().EnsureIndex(s => s.Name); diff --git a/src/Artemis.Storage/Repositories/SurfaceRepository.cs b/src/Artemis.Storage/Repositories/SurfaceRepository.cs index 8d58b7d39..85f5c6cbd 100644 --- a/src/Artemis.Storage/Repositories/SurfaceRepository.cs +++ b/src/Artemis.Storage/Repositories/SurfaceRepository.cs @@ -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().EnsureIndex(s => s.Name); diff --git a/src/Artemis.UI.Shared/Behaviors/PutCursorAtEndTextBoxBehavior.cs b/src/Artemis.UI.Shared/Behaviors/PutCursorAtEndTextBoxBehavior.cs index 011330894..9af87af13 100644 --- a/src/Artemis.UI.Shared/Behaviors/PutCursorAtEndTextBoxBehavior.cs +++ b/src/Artemis.UI.Shared/Behaviors/PutCursorAtEndTextBoxBehavior.cs @@ -4,7 +4,7 @@ using Microsoft.Xaml.Behaviors; namespace Artemis.UI.Shared { - public class PutCursorAtEndTextBoxBehavior : Behavior + public class PutCursorAtEndTextBox : Behavior { private TextBox _textBox; diff --git a/src/Artemis.UI.Shared/Behaviors/ScrollParentWhenAtMax.cs b/src/Artemis.UI.Shared/Behaviors/ScrollParentWhenAtMax.cs new file mode 100644 index 000000000..2cde2a73c --- /dev/null +++ b/src/Artemis.UI.Shared/Behaviors/ScrollParentWhenAtMax.cs @@ -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 + { + 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(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(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(v); + if (child != null) break; + } + + return child; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Converters/ColorGradientToGradientStopsConverter.cs b/src/Artemis.UI.Shared/Converters/ColorGradientToGradientStopsConverter.cs index ab795404e..22d9b951c 100644 --- a/src/Artemis.UI.Shared/Converters/ColorGradientToGradientStopsConverter.cs +++ b/src/Artemis.UI.Shared/Converters/ColorGradientToGradientStopsConverter.cs @@ -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 into a /// . /// - [ValueConversion(typeof(BindableCollection), typeof(GradientStopCollection))] + [ValueConversion(typeof(List), typeof(GradientStopCollection))] public class ColorGradientToGradientStopsConverter : IValueConverter { /// public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - BindableCollection colorGradients = (BindableCollection) value; + List colorGradients = (List) 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 colorGradients = new BindableCollection(); + List colorGradients = new List(); if (collection == null) return colorGradients; diff --git a/src/Artemis.UI.Shared/Services/DataModelUIService.cs b/src/Artemis.UI.Shared/Services/DataModelUIService.cs index d2399a3b8..55eda84b2 100644 --- a/src/Artemis.UI.Shared/Services/DataModelUIService.cs +++ b/src/Artemis.UI.Shared/Services/DataModelUIService.cs @@ -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 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); diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs index d32adef6e..de53e6b7c 100644 --- a/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs +++ b/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs @@ -7,22 +7,114 @@ using Artemis.UI.Shared.Input; namespace Artemis.UI.Shared.Services { + /// + /// A service for UI related data model tasks + /// public interface IDataModelUIService : IArtemisSharedUIService { + /// + /// Gets a read-only list of all registered data model editors + /// IReadOnlyCollection RegisteredDataModelEditors { get; } + + /// + /// Gets a read-only list of all registered data model displays + /// IReadOnlyCollection RegisteredDataModelDisplays { get; } + + /// + /// Creates a data model visualization view model for the main data model + /// + /// + /// A data model visualization view model containing all data model expansions and modules that expand the main + /// data model + /// DataModelPropertiesViewModel GetMainDataModelVisualization(); + + /// + /// Creates a data model visualization view model for the data model of the provided plugin feature + /// + /// The plugin feature to create hte data model visualization view model for + /// Whether or not also to include the main data model + /// A data model visualization view model containing the data model of the provided feature DataModelPropertiesViewModel GetPluginDataModelVisualization(PluginFeature pluginFeature, bool includeMainDataModel); + /// + /// Updates the children of the provided main data model visualization, removing disabled children and adding newly + /// enabled children + /// + void UpdateModules(DataModelPropertiesViewModel mainDataModelVisualization); + + /// + /// Registers a new data model editor + /// + /// The type of the editor + /// The plugin this editor belongs to + /// A collection of extra types this editor supports + /// A registration that can be used to remove the editor DataModelVisualizationRegistration RegisterDataModelInput(Plugin plugin, IReadOnlyCollection compatibleConversionTypes) where T : DataModelInputViewModel; + + /// + /// Registers a new data model display + /// + /// The type of the display + /// The plugin this display belongs to + /// A registration that can be used to remove the display DataModelVisualizationRegistration RegisterDataModelDisplay(Plugin plugin) where T : DataModelDisplayViewModel; + + /// + /// Removes a data model editor + /// + /// + /// The registration of the editor as returned by + /// void RemoveDataModelInput(DataModelVisualizationRegistration registration); + + /// + /// Removes a data model display + /// + /// + /// The registration of the display as returned by + /// void RemoveDataModelDisplay(DataModelVisualizationRegistration registration); + /// + /// Creates the most appropriate display view model for the provided that can display + /// a value + /// + /// The type of data model property to find a display view model for + /// The description of the data model property + /// + /// If , a simple .ToString() display view model will be + /// returned if nothing else is found + /// + /// The most appropriate display view model for the provided DataModelDisplayViewModel GetDataModelDisplayViewModel(Type propertyType, DataModelPropertyAttribute description, bool fallBackToDefault = false); + + /// + /// Creates the most appropriate input view model for the provided that allows + /// inputting a value + /// + /// The type of data model property to find a display view model for + /// The description of the data model property + /// The initial value to show in the input + /// A function to call whenever the input was updated (submitted or not) + /// The most appropriate input view model for the provided DataModelInputViewModel GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute description, object initialValue, Action updateCallback); + /// + /// Creates a view model that allows selecting a value from the data model + /// + /// + /// A view model that allows selecting a value from the data model DataModelDynamicViewModel GetDynamicSelectionViewModel(Module module); + + /// + /// Creates a view model that allows entering a value matching the target data model type + /// + /// The type of data model property to allow input for + /// The description of the target data model property + /// A view model that allows entering a value matching the target data model type DataModelStaticViewModel GetStaticInputViewModel(Type targetType, DataModelPropertyAttribute targetDescription); } } \ No newline at end of file diff --git a/src/Artemis.UI/Bootstrapper.cs b/src/Artemis.UI/Bootstrapper.cs index 7eb1fec3f..d64e10620 100644 --- a/src/Artemis.UI/Bootstrapper.cs +++ b/src/Artemis.UI/Bootstrapper.cs @@ -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(); @@ -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 diff --git a/src/Artemis.UI/DefaultTypes/DataModel/Input/DoubleDataModelInputView.xaml b/src/Artemis.UI/DefaultTypes/DataModel/Input/DoubleDataModelInputView.xaml index cd487c12e..003b7dee2 100644 --- a/src/Artemis.UI/DefaultTypes/DataModel/Input/DoubleDataModelInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/DataModel/Input/DoubleDataModelInputView.xaml @@ -16,7 +16,7 @@ LostFocus="{s:Action Submit}" Width="140"> - + \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/DataModel/Input/IntDataModelInputView.xaml b/src/Artemis.UI/DefaultTypes/DataModel/Input/IntDataModelInputView.xaml index eae99304e..41b49a299 100644 --- a/src/Artemis.UI/DefaultTypes/DataModel/Input/IntDataModelInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/DataModel/Input/IntDataModelInputView.xaml @@ -16,7 +16,7 @@ LostFocus="{s:Action Submit}" Width="140"> - + \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/DataModel/Input/StringDataModelInputView.xaml b/src/Artemis.UI/DefaultTypes/DataModel/Input/StringDataModelInputView.xaml index c93e9fbd1..5fefd4728 100644 --- a/src/Artemis.UI/DefaultTypes/DataModel/Input/StringDataModelInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/DataModel/Input/StringDataModelInputView.xaml @@ -10,7 +10,7 @@ d:DesignHeight="450" d:DesignWidth="800"> - + \ No newline at end of file diff --git a/src/Artemis.UI/Ninject/PluginUIModule.cs b/src/Artemis.UI/Ninject/PluginUIModule.cs index ae4d0d409..d6d4da758 100644 --- a/src/Artemis.UI/Ninject/PluginUIModule.cs +++ b/src/Artemis.UI/Ninject/PluginUIModule.cs @@ -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(); Bind(typeof(IModelValidator<>)).To(typeof(FluentValidationAdapter<>)); + Kernel.Bind(x => { x.From(Plugin.Assembly) diff --git a/src/Artemis.UI/Ninject/UiModule.cs b/src/Artemis.UI/Ninject/UiModule.cs index 9bfa0a102..13ead7e67 100644 --- a/src/Artemis.UI/Ninject/UiModule.cs +++ b/src/Artemis.UI/Ninject/UiModule.cs @@ -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(); + + Kernel.Bind().ToSelf().InSingletonScope(); + Kernel.Bind().ToSelf(); + // Bind all built-in VMs Kernel.Bind(x => { diff --git a/src/Artemis.UI/Ninject/ViewsSelfBindingResolver.cs b/src/Artemis.UI/Ninject/ViewsSelfBindingResolver.cs new file mode 100644 index 000000000..87e9a45f9 --- /dev/null +++ b/src/Artemis.UI/Ninject/ViewsSelfBindingResolver.cs @@ -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 +{ + /// + /// Represents a binding resolver that use the service in question itself as the target to activate but only if the service is a . + /// + public class UIElementSelfBindingResolver : NinjectComponent, IMissingBindingResolver + { + private readonly IConstructorScorer constructorScorer; + private readonly IPlanner planner; + + /// + /// Initializes a new instance of the class. + /// + /// The component. + /// The component. + public UIElementSelfBindingResolver(IPlanner planner, IConstructorScorer constructorScorer) + { + this.planner = planner; + this.constructorScorer = constructorScorer; + } + + /// + /// Returns a value indicating whether the specified service is self-bindable. + /// + /// The service. + /// + /// if the type is self-bindable; otherwise, . + /// + protected virtual bool TypeIsSelfBindable(Type service) + { + return !service.IsInterface + && !service.IsAbstract + && !service.IsValueType + && service != typeof(string) + && !service.ContainsGenericParameters; + } + + /// + /// Returns any bindings from the specified collection that match the specified service. + /// + /// The dictionary of all registered bindings. + /// The service in question. + /// + /// The series of matching bindings. + /// + public IEnumerable Resolve(Multimap bindings, IRequest request) + { + Type service = request.Service; + + if (!TypeIsSelfBindable(service) || service.IsAssignableFrom(typeof(UIElement))) + return Enumerable.Empty(); + + return new[] + { + new Binding(service) + { + ProviderCallback = ctx => new StandardProvider(service, planner, constructorScorer) + } + }; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml index f04946b2f..9c7d9b87f 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml @@ -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}"> - + @@ -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}"> - - - - - - - - - + ItemsSource="{Binding Modules}" /> @@ -73,12 +66,12 @@ [] - + - + diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs index 9b1e61659..c818e3cfd 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs @@ -28,6 +28,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs _updateTimer = new Timer(500); DisplayName = "Data model"; + Modules = new BindableCollection(); } public DataModelPropertiesViewModel MainDataModel @@ -42,11 +43,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs set => SetAndNotify(ref _propertySearch, value); } - public List Modules - { - get => _modules; - set => SetAndNotify(ref _modules, value); - } + public BindableCollection 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().Where(p => p.IsEnabled).ToList(); + Modules.Clear(); + Modules.AddRange(_pluginManagementService.GetFeaturesOfType().Where(p => p.IsEnabled).OrderBy(m => m.DisplayName)); + + if (SelectedModule == null) + _dataModelUIService.UpdateModules(MainDataModel); + else if (!SelectedModule.IsEnabled) + SelectedModule = null; } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Modules/ModuleOrderTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Modules/ModuleOrderTabViewModel.cs index ec0d8ad41..89c1335ec 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Modules/ModuleOrderTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Modules/ModuleOrderTabViewModel.cs @@ -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 _modules; + private List _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(pluginManagementService.GetFeaturesOfType().Select(m => new ModuleOrderModuleViewModel(m))); _defaultDropHandler = new DefaultDropHandler(); NormalModules = new BindableCollection(); ApplicationModules = new BindableCollection(); OverlayModules = new BindableCollection(); + } + protected override void OnActivate() + { + base.OnActivate(); Update(); } + protected override void OnDeactivate() + { + base.OnDeactivate(); + _modules = null; + } + public BindableCollection NormalModules { get; set; } public BindableCollection ApplicationModules { get; set; } public BindableCollection OverlayModules { get; set; } @@ -67,6 +76,8 @@ namespace Artemis.UI.Screens.Settings.Tabs.Modules public void Update() { + if (_modules == null) + _modules = _pluginManagementService.GetFeaturesOfType().Select(m => new ModuleOrderModuleViewModel(m)).ToList(); NormalModules.Clear(); NormalModules.AddRange(_modules.Where(m => m.Module.PriorityCategory == ModulePriorityCategory.Normal).OrderBy(m => m.Module.Priority)); diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs index 9ee01e301..d0fbc81ad 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs @@ -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)); } diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabView.xaml index efc2bd56d..13e5b8fc1 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabView.xaml @@ -9,10 +9,30 @@ mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:PluginSettingsTabViewModel}"> - - - The list below shows all loaded plugins. If you're missing something, view your logs folder. - + + + + + + + + + + + + + The list below shows all loaded plugins. If you're missing something, view your logs folder. + + + + + + @@ -24,6 +44,8 @@ - - + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabViewModel.cs index 34861783a..ffb530cd3 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabViewModel.cs @@ -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 _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 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(); diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml index ac3de8a64..8a6d5954e 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml @@ -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" /> - + - - - - - - Plugin enabled - - - - - + + + + + Plugin enabled + + + - + + + + + Plugin features - + Enable the plugin to view its features + + + + VirtualizingPanel.ScrollUnit="Pixel" + Visibility="{Binding IsEnabled, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"> + + + - - + diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs index a67c87fe4..8029aa330 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs @@ -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)); diff --git a/src/Artemis.UI/Screens/TrayViewModel.cs b/src/Artemis.UI/Screens/TrayViewModel.cs index 76b792bc4..a6c2386d1 100644 --- a/src/Artemis.UI/Screens/TrayViewModel.cs +++ b/src/Artemis.UI/Screens/TrayViewModel.cs @@ -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; diff --git a/src/Artemis.UI/Services/RegistrationService.cs b/src/Artemis.UI/Services/RegistrationService.cs index d70fb3db7..abfb32371 100644 --- a/src/Artemis.UI/Services/RegistrationService.cs +++ b/src/Artemis.UI/Services/RegistrationService.cs @@ -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)}); } }