mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Plugins - Implemented features enable/disable UI
This commit is contained in:
parent
e812929215
commit
c146479393
@ -7,6 +7,7 @@ using LiteDB;
|
||||
using Ninject.Activation;
|
||||
using Ninject.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)
|
||||
|
||||
@ -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());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
13
src/Artemis.Core/Services/Interfaces/IPluginService.cs
Normal file
13
src/Artemis.Core/Services/Interfaces/IPluginService.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace Artemis.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface for services provided by plugins.
|
||||
/// <para>
|
||||
/// Any service implementing this interface will be available inside the plugin as a singleton through dependency
|
||||
/// injection
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public interface IPluginService
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -185,7 +185,8 @@ namespace Artemis.Core.Services
|
||||
try
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
53
src/Artemis.UI.Shared/Behaviors/ScrollParentWhenAtMax.cs
Normal file
53
src/Artemis.UI.Shared/Behaviors/ScrollParentWhenAtMax.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Microsoft.Xaml.Behaviors;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
{
|
||||
public class ScrollParentWhenAtMax : Behavior<FrameworkElement>
|
||||
{
|
||||
protected override void OnAttached()
|
||||
{
|
||||
base.OnAttached();
|
||||
AssociatedObject.PreviewMouseWheel += PreviewMouseWheel;
|
||||
}
|
||||
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
AssociatedObject.PreviewMouseWheel -= PreviewMouseWheel;
|
||||
base.OnDetaching();
|
||||
}
|
||||
|
||||
private void PreviewMouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
ScrollViewer scrollViewer = GetVisualChild<ScrollViewer>(AssociatedObject);
|
||||
double scrollPos = scrollViewer.ContentVerticalOffset;
|
||||
if (scrollPos == scrollViewer.ScrollableHeight && e.Delta < 0
|
||||
|| scrollPos == 0 && e.Delta > 0)
|
||||
{
|
||||
e.Handled = true;
|
||||
MouseWheelEventArgs e2 = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta);
|
||||
e2.RoutedEvent = UIElement.MouseWheelEvent;
|
||||
AssociatedObject.RaiseEvent(e2);
|
||||
}
|
||||
}
|
||||
|
||||
private static T GetVisualChild<T>(DependencyObject parent) where T : Visual
|
||||
{
|
||||
T child = default;
|
||||
|
||||
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
|
||||
for (int i = 0; i < numVisuals; i++)
|
||||
{
|
||||
Visual v = (Visual) VisualTreeHelper.GetChild(parent, i);
|
||||
child = v as T;
|
||||
if (child == null) child = GetVisualChild<T>(v);
|
||||
if (child != null) break;
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.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;
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
LostFocus="{s:Action Submit}"
|
||||
Width="140">
|
||||
<b:Interaction.Behaviors>
|
||||
<shared:PutCursorAtEndTextBoxBehavior />
|
||||
<shared:PutCursorAtEndTextBox />
|
||||
</b:Interaction.Behaviors>
|
||||
</TextBox>
|
||||
</UserControl>
|
||||
@ -16,7 +16,7 @@
|
||||
LostFocus="{s:Action Submit}"
|
||||
Width="140">
|
||||
<b:Interaction.Behaviors>
|
||||
<shared:PutCursorAtEndTextBoxBehavior />
|
||||
<shared:PutCursorAtEndTextBox />
|
||||
</b:Interaction.Behaviors>
|
||||
</TextBox>
|
||||
</UserControl>
|
||||
@ -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>
|
||||
@ -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)
|
||||
|
||||
@ -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 =>
|
||||
{
|
||||
|
||||
75
src/Artemis.UI/Ninject/ViewsSelfBindingResolver.cs
Normal file
75
src/Artemis.UI/Ninject/ViewsSelfBindingResolver.cs
Normal file
@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using Ninject.Activation;
|
||||
using Ninject.Activation.Providers;
|
||||
using Ninject.Components;
|
||||
using Ninject.Infrastructure;
|
||||
using Ninject.Planning;
|
||||
using Ninject.Planning.Bindings;
|
||||
using Ninject.Planning.Bindings.Resolvers;
|
||||
using Ninject.Selection.Heuristics;
|
||||
|
||||
namespace Artemis.UI.Ninject
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a binding resolver that use the service in question itself as the target to activate but only if the service is a <see cref="UIElement"/>.
|
||||
/// </summary>
|
||||
public class UIElementSelfBindingResolver : NinjectComponent, IMissingBindingResolver
|
||||
{
|
||||
private readonly IConstructorScorer constructorScorer;
|
||||
private readonly IPlanner planner;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UIElementSelfBindingResolver" /> class.
|
||||
/// </summary>
|
||||
/// <param name="planner">The <see cref="IPlanner" /> component.</param>
|
||||
/// <param name="constructorScorer">The <see cref="IConstructorScorer" /> component.</param>
|
||||
public UIElementSelfBindingResolver(IPlanner planner, IConstructorScorer constructorScorer)
|
||||
{
|
||||
this.planner = planner;
|
||||
this.constructorScorer = constructorScorer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether the specified service is self-bindable.
|
||||
/// </summary>
|
||||
/// <param name="service">The service.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if the type is self-bindable; otherwise, <see langword="false" />.
|
||||
/// </returns>
|
||||
protected virtual bool TypeIsSelfBindable(Type service)
|
||||
{
|
||||
return !service.IsInterface
|
||||
&& !service.IsAbstract
|
||||
&& !service.IsValueType
|
||||
&& service != typeof(string)
|
||||
&& !service.ContainsGenericParameters;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns any bindings from the specified collection that match the specified service.
|
||||
/// </summary>
|
||||
/// <param name="bindings">The dictionary of all registered bindings.</param>
|
||||
/// <param name="request">The service in question.</param>
|
||||
/// <returns>
|
||||
/// The series of matching bindings.
|
||||
/// </returns>
|
||||
public IEnumerable<IBinding> Resolve(Multimap<Type, IBinding> bindings, IRequest request)
|
||||
{
|
||||
Type service = request.Service;
|
||||
|
||||
if (!TypeIsSelfBindable(service) || service.IsAssignableFrom(typeof(UIElement)))
|
||||
return Enumerable.Empty<IBinding>();
|
||||
|
||||
return new[]
|
||||
{
|
||||
new Binding(service)
|
||||
{
|
||||
ProviderCallback = ctx => new StandardProvider(service, planner, constructorScorer)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,10 +8,11 @@
|
||||
xmlns:wpf="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns: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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
@ -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();
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)});
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user