From 57d82fafa84579bb7943336dd5437946eee1a1ec Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Mon, 2 Mar 2020 22:15:25 +0100 Subject: [PATCH] Plugins - Implemented enabling/disabling (doesnt work for devices) Plugins - Disable plugins that caused a crash Tray icon - Added menu items for quick access --- .../Ninject/PluginSettingsProvider.cs | 8 +- src/Artemis.Core/Plugins/Models/PluginInfo.cs | 7 ++ .../Plugins/Models/PluginSetting.cs | 9 +- .../Plugins/Models/PluginSettings.cs | 13 ++- .../Services/Interfaces/IPluginService.cs | 12 ++ src/Artemis.Core/Services/PluginService.cs | 79 ++++++++++++- src/Artemis.Core/Services/RgbService.cs | 1 + src/Artemis.Core/Services/SettingsService.cs | 4 +- .../Entities/Plugins/PluginEntity.cs | 16 +++ .../{ => Plugins}/PluginSettingEntity.cs | 5 +- .../Interfaces/IPluginRepository.cs | 16 +++ .../Interfaces/IPluginSettingRepository.cs | 14 --- .../Repositories/PluginRepository.cs | 56 +++++++++ .../Repositories/PluginSettingRepository.cs | 40 ------- .../Artemis.UI.Shared.csproj | 5 - .../GradientEditor/GradientEditor.xaml | 39 ++++++- .../Events/RequestSelectSidebarItemEvent.cs | 12 ++ .../LayerPropertiesViewModel.cs | 15 ++- src/Artemis.UI/Screens/RootViewModel.cs | 1 - .../Screens/Settings/SettingsViewModel.cs | 5 +- .../Tabs/Plugins/PluginSettingsViewModel.cs | 24 +++- .../Screens/Sidebar/SidebarView.xaml | 2 +- .../Screens/Sidebar/SidebarViewModel.cs | 109 ++++++++++-------- src/Artemis.UI/Screens/TrayView.xaml | 24 +++- src/Artemis.UI/Screens/TrayViewModel.cs | 24 ++-- src/Artemis.sln | 6 + .../Settings/DeviceDefinition.cs | 14 +++ .../WS281XConfigurationViewModel.cs | 15 +-- .../WS281XDeviceProvider.cs | 31 ++++- 29 files changed, 442 insertions(+), 164 deletions(-) create mode 100644 src/Artemis.Storage/Entities/Plugins/PluginEntity.cs rename src/Artemis.Storage/Entities/{ => Plugins}/PluginSettingEntity.cs (59%) create mode 100644 src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs delete mode 100644 src/Artemis.Storage/Repositories/Interfaces/IPluginSettingRepository.cs create mode 100644 src/Artemis.Storage/Repositories/PluginRepository.cs delete mode 100644 src/Artemis.Storage/Repositories/PluginSettingRepository.cs create mode 100644 src/Artemis.UI/Events/RequestSelectSidebarItemEvent.cs create mode 100644 src/Plugins/Artemis.Plugins.Devices.WS281X/Settings/DeviceDefinition.cs diff --git a/src/Artemis.Core/Ninject/PluginSettingsProvider.cs b/src/Artemis.Core/Ninject/PluginSettingsProvider.cs index 43f3dc3d7..c2f780f6d 100644 --- a/src/Artemis.Core/Ninject/PluginSettingsProvider.cs +++ b/src/Artemis.Core/Ninject/PluginSettingsProvider.cs @@ -9,11 +9,11 @@ namespace Artemis.Core.Ninject { internal class PluginSettingsProvider : Provider { - private readonly IPluginSettingRepository _pluginSettingRepository; + private readonly IPluginRepository _pluginRepository; - internal PluginSettingsProvider(IPluginSettingRepository pluginSettingRepository) + internal PluginSettingsProvider(IPluginRepository pluginRepository) { - _pluginSettingRepository = pluginSettingRepository; + _pluginRepository = pluginRepository; } protected override PluginSettings CreateInstance(IContext context) @@ -25,7 +25,7 @@ namespace Artemis.Core.Ninject if (pluginInfo == null) throw new ArtemisCoreException("A plugin needs to be initialized with PluginInfo as a parameter"); - return new PluginSettings(pluginInfo, _pluginSettingRepository); + return new PluginSettings(pluginInfo, _pluginRepository); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Models/PluginInfo.cs b/src/Artemis.Core/Plugins/Models/PluginInfo.cs index 9c9bcd414..413085ae9 100644 --- a/src/Artemis.Core/Plugins/Models/PluginInfo.cs +++ b/src/Artemis.Core/Plugins/Models/PluginInfo.cs @@ -2,6 +2,7 @@ using System.IO; using System.Reflection; using Artemis.Core.Plugins.Abstract; +using Artemis.Storage.Entities.Plugins; using McMaster.NETCore.Plugins; using Newtonsoft.Json; @@ -72,6 +73,12 @@ namespace Artemis.Core.Plugins.Models [JsonIgnore] internal Assembly Assembly { get; set; } + /// + /// The entity representing the plugin + /// + [JsonIgnore] + internal PluginEntity PluginEntity { get; set; } + public override string ToString() { return $"{nameof(Guid)}: {Guid}, {nameof(Name)}: {Name}, {nameof(Version)}: {Version}"; diff --git a/src/Artemis.Core/Plugins/Models/PluginSetting.cs b/src/Artemis.Core/Plugins/Models/PluginSetting.cs index 88d5304de..31c926da8 100644 --- a/src/Artemis.Core/Plugins/Models/PluginSetting.cs +++ b/src/Artemis.Core/Plugins/Models/PluginSetting.cs @@ -1,5 +1,6 @@ using System; using Artemis.Storage.Entities; +using Artemis.Storage.Entities.Plugins; using Artemis.Storage.Repositories.Interfaces; using Newtonsoft.Json; @@ -10,13 +11,13 @@ namespace Artemis.Core.Plugins.Models // ReSharper disable once NotAccessedField.Local private readonly PluginInfo _pluginInfo; private readonly PluginSettingEntity _pluginSettingEntity; - private readonly IPluginSettingRepository _pluginSettingRepository; + private readonly IPluginRepository _pluginRepository; private T _value; - internal PluginSetting(PluginInfo pluginInfo, IPluginSettingRepository pluginSettingRepository, PluginSettingEntity pluginSettingEntity) + internal PluginSetting(PluginInfo pluginInfo, IPluginRepository pluginRepository, PluginSettingEntity pluginSettingEntity) { _pluginInfo = pluginInfo; - _pluginSettingRepository = pluginSettingRepository; + _pluginRepository = pluginRepository; _pluginSettingEntity = pluginSettingEntity; Name = pluginSettingEntity.Name; @@ -73,7 +74,7 @@ namespace Artemis.Core.Plugins.Models return; _pluginSettingEntity.Value = JsonConvert.SerializeObject(Value); - _pluginSettingRepository.Save(_pluginSettingEntity); + _pluginRepository.SaveSetting(_pluginSettingEntity); } public event EventHandler SettingChanged; diff --git a/src/Artemis.Core/Plugins/Models/PluginSettings.cs b/src/Artemis.Core/Plugins/Models/PluginSettings.cs index 4d915e5a0..870410fe8 100644 --- a/src/Artemis.Core/Plugins/Models/PluginSettings.cs +++ b/src/Artemis.Core/Plugins/Models/PluginSettings.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Artemis.Storage.Entities; +using Artemis.Storage.Entities.Plugins; using Artemis.Storage.Repositories.Interfaces; using Newtonsoft.Json; @@ -12,13 +13,13 @@ namespace Artemis.Core.Plugins.Models public class PluginSettings { private readonly PluginInfo _pluginInfo; - private readonly IPluginSettingRepository _pluginSettingRepository; + private readonly IPluginRepository _pluginRepository; private readonly Dictionary _settingEntities; - internal PluginSettings(PluginInfo pluginInfo, IPluginSettingRepository pluginSettingRepository) + internal PluginSettings(PluginInfo pluginInfo, IPluginRepository pluginRepository) { _pluginInfo = pluginInfo; - _pluginSettingRepository = pluginSettingRepository; + _pluginRepository = pluginRepository; _settingEntities = new Dictionary(); } @@ -37,15 +38,15 @@ namespace Artemis.Core.Plugins.Models if (_settingEntities.ContainsKey(name)) return (PluginSetting) _settingEntities[name]; // Try to find in database - var settingEntity = _pluginSettingRepository.GetByNameAndPluginGuid(name, _pluginInfo.Guid); + var settingEntity = _pluginRepository.GetSettingByNameAndGuid(name, _pluginInfo.Guid); // If not found, create a new one if (settingEntity == null) { settingEntity = new PluginSettingEntity {Name = name, PluginGuid = _pluginInfo.Guid, Value = JsonConvert.SerializeObject(defaultValue)}; - _pluginSettingRepository.Add(settingEntity); + _pluginRepository.AddSetting(settingEntity); } - var pluginSetting = new PluginSetting(_pluginInfo, _pluginSettingRepository, settingEntity); + var pluginSetting = new PluginSetting(_pluginInfo, _pluginRepository, settingEntity); _settingEntities.Add(name, pluginSetting); return pluginSetting; } diff --git a/src/Artemis.Core/Services/Interfaces/IPluginService.cs b/src/Artemis.Core/Services/Interfaces/IPluginService.cs index 57c102e97..f55ad9de6 100644 --- a/src/Artemis.Core/Services/Interfaces/IPluginService.cs +++ b/src/Artemis.Core/Services/Interfaces/IPluginService.cs @@ -42,6 +42,18 @@ namespace Artemis.Core.Services.Interfaces /// The plugin info defining the plugin to unload void UnloadPlugin(PluginInfo pluginInfo); + /// + /// Enables the provided plugin + /// + /// + void EnablePlugin(Plugin plugin); + + /// + /// Disables the provided plugin + /// + /// + void DisablePlugin(Plugin plugin); + /// /// Finds the plugin info related to the plugin /// diff --git a/src/Artemis.Core/Services/PluginService.cs b/src/Artemis.Core/Services/PluginService.cs index ce6125ae3..8d26c122a 100644 --- a/src/Artemis.Core/Services/PluginService.cs +++ b/src/Artemis.Core/Services/PluginService.cs @@ -10,6 +10,8 @@ using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Exceptions; using Artemis.Core.Plugins.Models; using Artemis.Core.Services.Interfaces; +using Artemis.Storage.Entities.Plugins; +using Artemis.Storage.Repositories.Interfaces; using McMaster.NETCore.Plugins; using Newtonsoft.Json; using Ninject; @@ -27,13 +29,15 @@ namespace Artemis.Core.Services { private readonly IKernel _kernel; private readonly ILogger _logger; + private readonly IPluginRepository _pluginRepository; private readonly List _plugins; private IKernel _childKernel; - internal PluginService(IKernel kernel, ILogger logger) + internal PluginService(IKernel kernel, ILogger logger, IPluginRepository pluginRepository) { _kernel = kernel; _logger = logger; + _pluginRepository = pluginRepository; _plugins = new List(); // Ensure the plugins directory exists @@ -146,6 +150,18 @@ namespace Artemis.Core.Services // Activate plugins after they are all loaded foreach (var pluginInfo in _plugins.Where(p => p.Enabled)) { + if (!pluginInfo.PluginEntity.LastEnableSuccessful) + { + pluginInfo.Enabled = false; + _logger.Warning("Plugin failed to load last time, disabling it now to avoid instability. Plugin info: {pluginInfo}", pluginInfo); + continue; + } + + // Mark this as false until the plugin enabled successfully and save it in case the plugin drags us down into a crash + pluginInfo.PluginEntity.LastEnableSuccessful = false; + _pluginRepository.SavePlugin(pluginInfo.PluginEntity); + + var threwException = false; try { pluginInfo.Instance.EnablePlugin(); @@ -153,6 +169,15 @@ namespace Artemis.Core.Services catch (Exception e) { _logger.Warning(new ArtemisPluginException(pluginInfo, "Failed to load enable plugin", e), "Plugin exception"); + pluginInfo.Enabled = false; + threwException = true; + } + + // We got this far so the plugin enabled and we didn't crash horribly, yay + if (!threwException) + { + pluginInfo.PluginEntity.LastEnableSuccessful = true; + _pluginRepository.SavePlugin(pluginInfo.PluginEntity); } OnPluginEnabled(new PluginEventArgs(pluginInfo)); @@ -191,8 +216,13 @@ namespace Artemis.Core.Services if (_plugins.Contains(pluginInfo)) UnloadPlugin(pluginInfo); - // TODO Just temporarily until settings are in place - pluginInfo.Enabled = true; + var pluginEntity = _pluginRepository.GetPluginByGuid(pluginInfo.Guid); + if (pluginEntity == null) + pluginEntity = new PluginEntity {PluginGuid = pluginInfo.Guid, IsEnabled = true, LastEnableSuccessful = true}; + + pluginInfo.PluginEntity = pluginEntity; + pluginInfo.Enabled = pluginEntity.IsEnabled; + var mainFile = Path.Combine(pluginInfo.Directory.FullName, pluginInfo.Main); if (!File.Exists(mainFile)) throw new ArtemisPluginException(pluginInfo, "Couldn't find the plugins main entry at " + mainFile); @@ -277,6 +307,49 @@ namespace Artemis.Core.Services } } + public void EnablePlugin(Plugin plugin) + { + plugin.PluginInfo.Enabled = true; + plugin.PluginInfo.PluginEntity.IsEnabled = true; + plugin.PluginInfo.PluginEntity.LastEnableSuccessful = false; + _pluginRepository.SavePlugin(plugin.PluginInfo.PluginEntity); + + var threwException = false; + try + { + plugin.EnablePlugin(); + } + catch (Exception e) + { + _logger.Warning(new ArtemisPluginException(plugin.PluginInfo, "Failed to enable plugin", e), "Plugin exception"); + plugin.PluginInfo.Enabled = false; + threwException = true; + } + + // We got this far so the plugin enabled and we didn't crash horribly, yay + if (!threwException) + { + plugin.PluginInfo.PluginEntity.LastEnableSuccessful = true; + _pluginRepository.SavePlugin(plugin.PluginInfo.PluginEntity); + } + + OnPluginEnabled(new PluginEventArgs(plugin.PluginInfo)); + } + + public void DisablePlugin(Plugin plugin) + { + plugin.PluginInfo.Enabled = false; + plugin.PluginInfo.PluginEntity.IsEnabled = false; + _pluginRepository.SavePlugin(plugin.PluginInfo.PluginEntity); + + plugin.DisablePlugin(); + + // We got this far so the plugin enabled and we didn't crash horribly, yay + _pluginRepository.SavePlugin(plugin.PluginInfo.PluginEntity); + + OnPluginDisabled(new PluginEventArgs(plugin.PluginInfo)); + } + /// public PluginInfo GetPluginInfo(Plugin plugin) { diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index 3d72e44ce..4c7944eb7 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -61,6 +61,7 @@ namespace Artemis.Core.Services catch (Exception e) { _logger.Error(e, "Exception during device loading for device provider {deviceProvider}", deviceProvider.GetType().Name); + throw e; } if (deviceProvider.Devices == null) diff --git a/src/Artemis.Core/Services/SettingsService.cs b/src/Artemis.Core/Services/SettingsService.cs index 5e855a2af..86dcf6100 100644 --- a/src/Artemis.Core/Services/SettingsService.cs +++ b/src/Artemis.Core/Services/SettingsService.cs @@ -9,9 +9,9 @@ namespace Artemis.Core.Services { private readonly PluginSettings _pluginSettings; - internal SettingsService(IPluginSettingRepository pluginSettingRepository) + internal SettingsService(IPluginRepository pluginRepository) { - _pluginSettings = new PluginSettings(Constants.CorePluginInfo, pluginSettingRepository); + _pluginSettings = new PluginSettings(Constants.CorePluginInfo, pluginRepository); } public PluginSetting GetSetting(string name, T defaultValue = default) diff --git a/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs b/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs new file mode 100644 index 000000000..5f0091f3b --- /dev/null +++ b/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs @@ -0,0 +1,16 @@ +using System; + +namespace Artemis.Storage.Entities.Plugins +{ + /// + /// Represents the configuration of a plugin, each plugin has one configuration + /// + public class PluginEntity + { + public Guid Id { get; set; } + public Guid PluginGuid { get; set; } + + public bool IsEnabled { get; set; } + public bool LastEnableSuccessful { get; set; } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/PluginSettingEntity.cs b/src/Artemis.Storage/Entities/Plugins/PluginSettingEntity.cs similarity index 59% rename from src/Artemis.Storage/Entities/PluginSettingEntity.cs rename to src/Artemis.Storage/Entities/Plugins/PluginSettingEntity.cs index 7b63df153..db0712c7b 100644 --- a/src/Artemis.Storage/Entities/PluginSettingEntity.cs +++ b/src/Artemis.Storage/Entities/Plugins/PluginSettingEntity.cs @@ -1,7 +1,10 @@ using System; -namespace Artemis.Storage.Entities +namespace Artemis.Storage.Entities.Plugins { + /// + /// Represents the setting of a plugin, a plugin can have multiple settings + /// public class PluginSettingEntity { public Guid Id { get; set; } diff --git a/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs new file mode 100644 index 000000000..9a1258416 --- /dev/null +++ b/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs @@ -0,0 +1,16 @@ +using System; +using Artemis.Storage.Entities.Plugins; + +namespace Artemis.Storage.Repositories.Interfaces +{ + public interface IPluginRepository : IRepository + { + void AddPlugin(PluginEntity pluginEntity); + PluginEntity GetPluginByGuid(Guid pluginGuid); + void SavePlugin(PluginEntity pluginEntity); + void AddSetting(PluginSettingEntity pluginSettingEntity); + PluginSettingEntity GetSettingByGuid(Guid pluginGuid); + PluginSettingEntity GetSettingByNameAndGuid(string name, Guid pluginGuid); + void SaveSetting(PluginSettingEntity pluginSettingEntity); + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/IPluginSettingRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IPluginSettingRepository.cs deleted file mode 100644 index 3cf79cae1..000000000 --- a/src/Artemis.Storage/Repositories/Interfaces/IPluginSettingRepository.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using Artemis.Storage.Entities; - -namespace Artemis.Storage.Repositories.Interfaces -{ - public interface IPluginSettingRepository : IRepository - { - void Add(PluginSettingEntity pluginSettingEntity); - List GetByPluginGuid(Guid pluginGuid); - PluginSettingEntity GetByNameAndPluginGuid(string name, Guid pluginGuid); - void Save(PluginSettingEntity pluginSettingEntity); - } -} \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/PluginRepository.cs b/src/Artemis.Storage/Repositories/PluginRepository.cs new file mode 100644 index 000000000..2dfb52076 --- /dev/null +++ b/src/Artemis.Storage/Repositories/PluginRepository.cs @@ -0,0 +1,56 @@ +using System; +using Artemis.Storage.Entities.Plugins; +using Artemis.Storage.Repositories.Interfaces; +using LiteDB; + +namespace Artemis.Storage.Repositories +{ + public class PluginRepository : IPluginRepository + { + private readonly LiteRepository _repository; + + internal PluginRepository(LiteRepository repository) + { + _repository = repository; + + _repository.Database.GetCollection().EnsureIndex(s => s.PluginGuid); + _repository.Database.GetCollection().EnsureIndex(s => s.Name); + _repository.Database.GetCollection().EnsureIndex(s => s.PluginGuid); + } + + public void AddPlugin(PluginEntity pluginEntity) + { + _repository.Insert(pluginEntity); + } + + public PluginEntity GetPluginByGuid(Guid pluginGuid) + { + return _repository.FirstOrDefault(p => p.PluginGuid == pluginGuid); + } + + public void SavePlugin(PluginEntity pluginEntity) + { + _repository.Upsert(pluginEntity); + } + + public void AddSetting(PluginSettingEntity pluginSettingEntity) + { + _repository.Insert(pluginSettingEntity); + } + + public PluginSettingEntity GetSettingByGuid(Guid pluginGuid) + { + return _repository.FirstOrDefault(p => p.PluginGuid == pluginGuid); + } + + public PluginSettingEntity GetSettingByNameAndGuid(string name, Guid pluginGuid) + { + return _repository.FirstOrDefault(p => p.Name == name && p.PluginGuid == pluginGuid); + } + + public void SaveSetting(PluginSettingEntity pluginSettingEntity) + { + _repository.Upsert(pluginSettingEntity); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/PluginSettingRepository.cs b/src/Artemis.Storage/Repositories/PluginSettingRepository.cs deleted file mode 100644 index a50de948f..000000000 --- a/src/Artemis.Storage/Repositories/PluginSettingRepository.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Collections.Generic; -using Artemis.Storage.Entities; -using Artemis.Storage.Repositories.Interfaces; -using LiteDB; - -namespace Artemis.Storage.Repositories -{ - public class PluginSettingRepository : IPluginSettingRepository - { - private readonly LiteRepository _repository; - - internal PluginSettingRepository(LiteRepository repository) - { - _repository = repository; - _repository.Database.GetCollection().EnsureIndex(s => s.Name); - _repository.Database.GetCollection().EnsureIndex(s => s.PluginGuid); - } - - public void Add(PluginSettingEntity pluginSettingEntity) - { - _repository.Insert(pluginSettingEntity); - } - - public List GetByPluginGuid(Guid pluginGuid) - { - return _repository.Query().Where(p => p.PluginGuid == pluginGuid).ToList(); - } - - public PluginSettingEntity GetByNameAndPluginGuid(string name, Guid pluginGuid) - { - return _repository.FirstOrDefault(p => p.Name == name && p.PluginGuid == pluginGuid); - } - - public void Save(PluginSettingEntity pluginSettingEntity) - { - _repository.Upsert(pluginSettingEntity); - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj index f52f5238e..100d13b6a 100644 --- a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj +++ b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj @@ -49,11 +49,6 @@ false - - - ..\..\..\..\.nuget\materialdesignextensions\3.0.0\lib\netcoreapp3.0\MaterialDesignExtensions.dll - - diff --git a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditor.xaml b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditor.xaml index 480f0a270..25b336f48 100644 --- a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditor.xaml +++ b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditor.xaml @@ -7,6 +7,7 @@ xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions" xmlns:s="https://github.com/canton7/Stylet" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" + xmlns:shared="clr-namespace:Artemis.UI.Shared" mc:Ignorable="d" Title="Gradient Editor" Background="{DynamicResource MaterialDesignPaper}" @@ -18,7 +19,41 @@ Icon="/Resources/Images/Logo/logo-512.png" d:DesignHeight="450" d:DesignWidth="800"> - + + + + + + Gradient saving not implemented yet + + + Soon you'll be able to store different gradients for usage throughout your profiles and quickly select them + + + - + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Events/RequestSelectSidebarItemEvent.cs b/src/Artemis.UI/Events/RequestSelectSidebarItemEvent.cs new file mode 100644 index 000000000..638eeec0a --- /dev/null +++ b/src/Artemis.UI/Events/RequestSelectSidebarItemEvent.cs @@ -0,0 +1,12 @@ +namespace Artemis.UI.Events +{ + public class RequestSelectSidebarItemEvent + { + public string Label { get; } + + public RequestSelectSidebarItemEvent(string label) + { + Label = label; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index 39c67ec2a..712dda3f1 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -23,6 +23,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties private readonly ICoreService _coreService; private readonly List _layerPropertyViewModels; private readonly ILayerPropertyVmFactory _layerPropertyVmFactory; + private readonly IPropertyTreeVmFactory _propertyTreeVmFactory; + private readonly IPropertyTimelineVmFactory _propertyTimelineVmFactory; private readonly IProfileEditorService _profileEditorService; private readonly ISettingsService _settingsService; @@ -37,14 +39,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties _coreService = coreService; _settingsService = settingsService; _layerPropertyVmFactory = layerPropertyVmFactory; + _propertyTreeVmFactory = propertyTreeVmFactory; + _propertyTimelineVmFactory = propertyTimelineVmFactory; _layerPropertyViewModels = new List(); PixelsPerSecond = 31; - PropertyTree = propertyTreeVmFactory.Create(this); - PropertyTimeline = propertyTimelineVmFactory.Create(this); - - PopulateProperties(_profileEditorService.SelectedProfileElement, null); - } + } public bool Playing { get; set; } public bool RepeatAfterLastKeyframe { get; set; } @@ -71,6 +71,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties protected override void OnInitialActivate() { + PropertyTree = _propertyTreeVmFactory.Create(this); + PropertyTimeline = _propertyTimelineVmFactory.Create(this); + + PopulateProperties(_profileEditorService.SelectedProfileElement, null); + _profileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected; _profileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged; diff --git a/src/Artemis.UI/Screens/RootViewModel.cs b/src/Artemis.UI/Screens/RootViewModel.cs index 4a494f6a9..da6f4a4f7 100644 --- a/src/Artemis.UI/Screens/RootViewModel.cs +++ b/src/Artemis.UI/Screens/RootViewModel.cs @@ -2,7 +2,6 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Input; -using Artemis.Core.Services; using Artemis.UI.Events; using Artemis.UI.Screens.Sidebar; using Artemis.UI.Utilities; diff --git a/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs index 7d16ce5f6..38cdec0ec 100644 --- a/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs @@ -186,10 +186,9 @@ namespace Artemis.UI.Screens.Settings foreach (var device in _surfaceService.ActiveSurface.Devices) DeviceSettingsViewModels.Add(_deviceSettingsVmFactory.Create(device)); - // TODO: GetPluginsOfType isn't ideal here as it doesn't include disabled plugins Plugins.Clear(); - foreach (var plugin in _pluginService.GetPluginsOfType()) - Plugins.Add(new PluginSettingsViewModel(plugin, _windowManager, _dialogService)); + foreach (var pluginInfo in _pluginService.GetAllPluginInfo()) + Plugins.Add(new PluginSettingsViewModel(pluginInfo.Instance, _windowManager, _dialogService, _pluginService)); base.OnInitialActivate(); } diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs index 967324e6a..550f06689 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Artemis.Core.Plugins.Abstract; +using Artemis.Core.Services.Interfaces; using Artemis.UI.Shared.Services.Interfaces; using Stylet; @@ -10,21 +11,27 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins { private readonly IDialogService _dialogService; private readonly Plugin _plugin; + private readonly IPluginService _pluginService; private readonly IWindowManager _windowManager; - public PluginSettingsViewModel(Plugin plugin, IWindowManager windowManager, IDialogService dialogService) + public PluginSettingsViewModel(Plugin plugin, IWindowManager windowManager, IDialogService dialogService, IPluginService pluginService) { _plugin = plugin; _windowManager = windowManager; _dialogService = dialogService; - IsEnabled = true; + _pluginService = pluginService; } public string Type => _plugin.GetType().BaseType?.Name ?? _plugin.GetType().Name; public string Name => _plugin.PluginInfo.Name; public string Description => _plugin.PluginInfo.Description; public Version Version => _plugin.PluginInfo.Version; - public bool IsEnabled { get; set; } + + public bool IsEnabled + { + get => _plugin.PluginInfo.Enabled; + set => Task.Run(() => UpdateEnabled(value)); + } public bool CanOpenSettings => IsEnabled && _plugin.HasConfigurationViewModel; @@ -42,5 +49,16 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins throw; } } + + private void UpdateEnabled(in bool enable) + { + if (_plugin.PluginInfo.Enabled == enable) + return; + + if (enable) + _pluginService.EnablePlugin(_plugin); + else + _pluginService.DisablePlugin(_plugin); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarView.xaml b/src/Artemis.UI/Screens/Sidebar/SidebarView.xaml index a13251793..c0e13a66d 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarView.xaml +++ b/src/Artemis.UI/Screens/Sidebar/SidebarView.xaml @@ -25,7 +25,7 @@ - + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs index e3dec7414..c98c8b165 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs +++ b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Artemis.Core.Events; using Artemis.Core.Services.Interfaces; +using Artemis.UI.Events; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.Home; using Artemis.UI.Screens.News; @@ -18,13 +19,13 @@ using Stylet; namespace Artemis.UI.Screens.Sidebar { - public class SidebarViewModel : PropertyChangedBase + public class SidebarViewModel : PropertyChangedBase, IHandle { private readonly IKernel _kernel; private readonly IModuleVmFactory _moduleVmFactory; private readonly IPluginService _pluginService; - public SidebarViewModel(IKernel kernel, IModuleVmFactory moduleVmFactory, IPluginService pluginService) + public SidebarViewModel(IKernel kernel, IEventAggregator eventAggregator, IModuleVmFactory moduleVmFactory, IPluginService pluginService) { _kernel = kernel; _moduleVmFactory = moduleVmFactory; @@ -36,6 +37,8 @@ namespace Artemis.UI.Screens.Sidebar SetupSidebar(); _pluginService.PluginEnabled += PluginServiceOnPluginEnabled; _pluginService.PluginDisabled += PluginServiceOnPluginDisabled; + + eventAggregator.Subscribe(this); } public BindableCollection SidebarItems { get; set; } @@ -66,53 +69,6 @@ namespace Artemis.UI.Screens.Sidebar Task.Run(() => SelectSidebarItem(SidebarItems[1])); } - private async Task SelectSidebarItem(INavigationItem sidebarItem) - { - // A module was selected if the dictionary contains the selected item - if (SidebarModules.ContainsKey(sidebarItem)) - await ActivateModule(sidebarItem); - else if (sidebarItem is FirstLevelNavigationItem navigationItem) - { - if (navigationItem.Label == "Home") - await ActivateViewModel(); - else if (navigationItem.Label == "News") - await ActivateViewModel(); - else if (navigationItem.Label == "Workshop") - await ActivateViewModel(); - else if (navigationItem.Label == "Surface Editor") - await ActivateViewModel(); - else if (navigationItem.Label == "Settings") - await ActivateViewModel(); - } - else if (await CloseCurrentItem()) - SelectedItem = null; - } - - private async Task CloseCurrentItem() - { - if (SelectedItem == null) - return true; - - var canClose = await SelectedItem.CanCloseAsync(); - if (!canClose) - return false; - - SelectedItem.Close(); - return true; - } - - private async Task ActivateViewModel() - { - if (await CloseCurrentItem()) - SelectedItem = (IScreen) _kernel.Get(); - } - - private async Task ActivateModule(INavigationItem sidebarItem) - { - if (await CloseCurrentItem()) - SelectedItem = SidebarModules.ContainsKey(sidebarItem) ? _moduleVmFactory.Create(SidebarModules[sidebarItem]) : null; - } - // ReSharper disable once UnusedMember.Global - Called by view public async Task SelectItem(WillSelectNavigationItemEventArgs args) { @@ -151,6 +107,56 @@ namespace Artemis.UI.Screens.Sidebar SidebarModules.Remove(existing.Key); } + private async Task SelectSidebarItem(INavigationItem sidebarItem) + { + // A module was selected if the dictionary contains the selected item + if (SidebarModules.ContainsKey(sidebarItem)) + await ActivateModule(sidebarItem); + else if (sidebarItem is FirstLevelNavigationItem navigationItem) + await ActivateViewModel(navigationItem.Label); + else if (await CloseCurrentItem()) + SelectedItem = null; + } + + private async Task CloseCurrentItem() + { + if (SelectedItem == null) + return true; + + var canClose = await SelectedItem.CanCloseAsync(); + if (!canClose) + return false; + + SelectedItem.Close(); + return true; + } + + private async Task ActivateViewModel(string label) + { + if (label == "Home") + await ActivateViewModel(); + else if (label == "News") + await ActivateViewModel(); + else if (label == "Workshop") + await ActivateViewModel(); + else if (label == "Surface Editor") + await ActivateViewModel(); + else if (label == "Settings") + await ActivateViewModel(); + } + + private async Task ActivateViewModel() + { + if (await CloseCurrentItem()) + SelectedItem = (IScreen) _kernel.Get(); + } + + private async Task ActivateModule(INavigationItem sidebarItem) + { + if (await CloseCurrentItem()) + SelectedItem = SidebarModules.ContainsKey(sidebarItem) ? _moduleVmFactory.Create(SidebarModules[sidebarItem]) : null; + } + #region Event handlers private void PluginServiceOnPluginEnabled(object sender, PluginEventArgs e) @@ -165,6 +171,11 @@ namespace Artemis.UI.Screens.Sidebar RemoveModule(module); } + public void Handle(RequestSelectSidebarItemEvent message) + { + Execute.OnUIThread(async () => await ActivateViewModel(message.Label)); + } + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/TrayView.xaml b/src/Artemis.UI/Screens/TrayView.xaml index 96f958f09..bcf4665d2 100644 --- a/src/Artemis.UI/Screens/TrayView.xaml +++ b/src/Artemis.UI/Screens/TrayView.xaml @@ -20,9 +20,29 @@ DoubleClickCommand="{s:Action TrayBringToForeground}"> - + - + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Artemis.UI/Screens/TrayViewModel.cs b/src/Artemis.UI/Screens/TrayViewModel.cs index d563fe193..8ff99eb65 100644 --- a/src/Artemis.UI/Screens/TrayViewModel.cs +++ b/src/Artemis.UI/Screens/TrayViewModel.cs @@ -1,6 +1,7 @@ using System.Windows; using Artemis.Core.Services; using Artemis.Core.Services.Interfaces; +using Artemis.UI.Events; using Artemis.UI.Screens.Splash; using Ninject; using Stylet; @@ -9,15 +10,16 @@ namespace Artemis.UI.Screens { public class TrayViewModel : Screen { - private readonly ICoreService _coreService; private readonly IKernel _kernel; private readonly IWindowManager _windowManager; + private readonly IEventAggregator _eventAggregator; + private SplashViewModel _splashViewModel; - public TrayViewModel(IKernel kernel, IWindowManager windowManager, ICoreService coreService, ISettingsService settingsService) + public TrayViewModel(IKernel kernel, IWindowManager windowManager, IEventAggregator eventAggregator, ICoreService coreService, ISettingsService settingsService) { _kernel = kernel; _windowManager = windowManager; - _coreService = coreService; + _eventAggregator = eventAggregator; CanShowRootViewModel = true; var autoRunning = Bootstrapper.StartupArguments.Contains("-autorun"); @@ -25,7 +27,7 @@ namespace Artemis.UI.Screens if (!autoRunning || showOnAutoRun) { ShowSplashScreen(); - _coreService.Initialized += (sender, args) => TrayBringToForeground(); + coreService.Initialized += (sender, args) => TrayBringToForeground(); } } @@ -35,16 +37,24 @@ namespace Artemis.UI.Screens { if (!CanShowRootViewModel) return; - CanShowRootViewModel = false; + CanShowRootViewModel = false; Execute.OnUIThread(() => { + _splashViewModel?.RequestClose(); + _splashViewModel = null; var rootViewModel = _kernel.Get(); rootViewModel.Closed += RootViewModelOnClosed; _windowManager.ShowWindow(rootViewModel); }); } + public void TrayActivateSidebarItem(string sidebarItem) + { + TrayBringToForeground(); + _eventAggregator.Publish(new RequestSelectSidebarItemEvent(sidebarItem)); + } + public void TrayExit() { Application.Current.Shutdown(); @@ -54,8 +64,8 @@ namespace Artemis.UI.Screens { Execute.OnUIThread(() => { - var splashViewModel = _kernel.Get(); - _windowManager.ShowWindow(splashViewModel); + _splashViewModel = _kernel.Get(); + _windowManager.ShowWindow(_splashViewModel); }); } diff --git a/src/Artemis.sln b/src/Artemis.sln index 30e464c16..f3967c70c 100644 --- a/src/Artemis.sln +++ b/src/Artemis.sln @@ -5,13 +5,19 @@ VisualStudioVersion = 16.0.28729.10 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.UI", "Artemis.UI\Artemis.UI.csproj", "{46B74153-77CF-4489-BDF9-D53FDB1F7ACB}" ProjectSection(ProjectDependencies) = postProject + {07678400-2FE1-4C6E-A8D4-4F9F3C0630EA} = {07678400-2FE1-4C6E-A8D4-4F9F3C0630EA} {AB80F106-5444-46AA-A255-F765DD2F04F1} = {AB80F106-5444-46AA-A255-F765DD2F04F1} {8DC7960F-6DDF-4007-A155-17E124F39374} = {8DC7960F-6DDF-4007-A155-17E124F39374} {DCF7C321-95DC-4507-BB61-A7C5356E58EC} = {DCF7C321-95DC-4507-BB61-A7C5356E58EC} {E592F239-FAA0-4840-9C85-46E5867D06D5} = {E592F239-FAA0-4840-9C85-46E5867D06D5} + {36C10640-A31F-4DEE-9F0E-9B9E3F12753D} = {36C10640-A31F-4DEE-9F0E-9B9E3F12753D} {0F288A66-6EB0-4589-8595-E33A3A3EAEA2} = {0F288A66-6EB0-4589-8595-E33A3A3EAEA2} + {A46F278A-FC2C-4342-8455-994D957DDA03} = {A46F278A-FC2C-4342-8455-994D957DDA03} + {26902C94-3EBC-4132-B7F0-FFCAB8E150DA} = {26902C94-3EBC-4132-B7F0-FFCAB8E150DA} {7F4C7AB0-4C9B-452D-AFED-34544C903DEF} = {7F4C7AB0-4C9B-452D-AFED-34544C903DEF} {235A45C7-24AD-4F47-B9D4-CD67E610A04D} = {235A45C7-24AD-4F47-B9D4-CD67E610A04D} + {D004FEC9-0CF8-4828-B620-95DBA73201A3} = {D004FEC9-0CF8-4828-B620-95DBA73201A3} + {FA5815D3-EA87-4A64-AD6C-A5AE96C61F29} = {FA5815D3-EA87-4A64-AD6C-A5AE96C61F29} {C6BDB6D9-062D-4C28-A280-F3BD6197F07F} = {C6BDB6D9-062D-4C28-A280-F3BD6197F07F} {A779B2F8-C253-4C4B-8634-6EB8F594E96D} = {A779B2F8-C253-4C4B-8634-6EB8F594E96D} EndProjectSection diff --git a/src/Plugins/Artemis.Plugins.Devices.WS281X/Settings/DeviceDefinition.cs b/src/Plugins/Artemis.Plugins.Devices.WS281X/Settings/DeviceDefinition.cs new file mode 100644 index 000000000..d86cdd1a5 --- /dev/null +++ b/src/Plugins/Artemis.Plugins.Devices.WS281X/Settings/DeviceDefinition.cs @@ -0,0 +1,14 @@ +namespace Artemis.Plugins.Devices.WS281X.Settings +{ + public class DeviceDefinition + { + public DeviceDefinitionType Type { get; set; } + public string Port { get; set; } + } + + public enum DeviceDefinitionType + { + Arduino, + Bitwizard + } +} \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Devices.WS281X/ViewModels/WS281XConfigurationViewModel.cs b/src/Plugins/Artemis.Plugins.Devices.WS281X/ViewModels/WS281XConfigurationViewModel.cs index ba8187d0c..941722396 100644 --- a/src/Plugins/Artemis.Plugins.Devices.WS281X/ViewModels/WS281XConfigurationViewModel.cs +++ b/src/Plugins/Artemis.Plugins.Devices.WS281X/ViewModels/WS281XConfigurationViewModel.cs @@ -1,17 +1,18 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Collections.Generic; using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Abstract.ViewModels; +using Artemis.Core.Plugins.Models; +using Artemis.Plugins.Devices.WS281X.Settings; namespace Artemis.Plugins.Devices.WS281X.ViewModels { public class WS281XConfigurationViewModel : PluginConfigurationViewModel { - public WS281XConfigurationViewModel(Plugin plugin) : base(plugin) + private PluginSetting> _definitions; + + public WS281XConfigurationViewModel(Plugin plugin, PluginSettings settings) : base(plugin) { - var WS281XInstance = RGB.NET.Devices.WS281X.WS281XDeviceProvider.Instance; - + _definitions = settings.GetSetting>("DeviceDefinitions"); } } -} +} \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.Devices.WS281X/WS281XDeviceProvider.cs b/src/Plugins/Artemis.Plugins.Devices.WS281X/WS281XDeviceProvider.cs index 8a866ff60..82a2e763e 100644 --- a/src/Plugins/Artemis.Plugins.Devices.WS281X/WS281XDeviceProvider.cs +++ b/src/Plugins/Artemis.Plugins.Devices.WS281X/WS281XDeviceProvider.cs @@ -1,26 +1,47 @@ -using Artemis.Core.Plugins.Abstract; +using System; +using System.Collections.Generic; +using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Abstract.ViewModels; using Artemis.Core.Plugins.Models; using Artemis.Core.Services.Interfaces; +using Artemis.Plugins.Devices.WS281X.Settings; using Artemis.Plugins.Devices.WS281X.ViewModels; +using RGB.NET.Devices.WS281X.Arduino; +using RGB.NET.Devices.WS281X.Bitwizard; namespace Artemis.Plugins.Devices.WS281X { // ReSharper disable once UnusedMember.Global public class WS281XDeviceProvider : DeviceProvider { + public PluginSettings Settings { get; } private readonly IRgbService _rgbService; - public WS281XDeviceProvider(PluginInfo pluginInfo, IRgbService rgbService) : base(pluginInfo, RGB.NET.Devices.WS281X.WS281XDeviceProvider.Instance) + public WS281XDeviceProvider(PluginInfo pluginInfo, IRgbService rgbService, PluginSettings settings) : base(pluginInfo, RGB.NET.Devices.WS281X.WS281XDeviceProvider.Instance) { + Settings = settings; _rgbService = rgbService; HasConfigurationViewModel = true; } public override void EnablePlugin() { - // TODO: Load from configuration - //RGB.NET.Devices.WS281X.WS281XDeviceProvider.Instance.AddDeviceDefinition(); + var definitions = Settings.GetSetting>("DeviceDefinitions"); + foreach (var deviceDefinition in definitions.Value) + { + switch (deviceDefinition.Type) + { + case DeviceDefinitionType.Arduino: + RGB.NET.Devices.WS281X.WS281XDeviceProvider.Instance.AddDeviceDefinition(new ArduinoWS281XDeviceDefinition(deviceDefinition.Port)); + break; + case DeviceDefinitionType.Bitwizard: + RGB.NET.Devices.WS281X.WS281XDeviceProvider.Instance.AddDeviceDefinition(new BitwizardWS281XDeviceDefinition(deviceDefinition.Port)); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + _rgbService.AddDeviceProvider(RgbDeviceProvider); } @@ -38,7 +59,7 @@ namespace Artemis.Plugins.Devices.WS281X public override PluginConfigurationViewModel GetConfigurationViewModel() { - return new WS281XConfigurationViewModel(this); + return new WS281XConfigurationViewModel(this, Settings); } } } \ No newline at end of file