From ed479abebcf2fbac52c54206fad645160f8343ab Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 24 Aug 2020 19:23:29 +0200 Subject: [PATCH] Modules - Added enable override mechanism Profile modules - Added animated module enable/disable --- .../ProcessActivationRequirement.cs | 5 +- src/Artemis.Core/Plugins/Modules/Module.cs | 30 ++-- .../Plugins/Modules/ProfileModule.cs | 4 +- src/Artemis.Core/Services/CoreService.cs | 4 +- .../Services/Interfaces/IModuleService.cs | 41 ++++++ .../Services/Interfaces/IPluginService.cs | 3 + src/Artemis.Core/Services/ModuleService.cs | 134 +++++++++++++----- .../Storage/Interfaces/IProfileService.cs | 8 ++ .../Services/Storage/ProfileService.cs | 23 +++ src/Artemis.Core/Utilities/IntroAnimation.cs | 4 +- ...is - Backup.Plugins.Modules.Overlay.csproj | 46 ++++++ .../Artemis.Plugins.Modules.Overlay.csproj | 35 +++++ .../OverlayModule.cs | 35 +++++ .../plugin.json | 7 + .../Screens/Module/ModuleRootViewModel.cs | 27 +++- .../ProfileEditor/ProfileEditorViewModel.cs | 6 +- src/Artemis.UI/Screens/RootViewModel.cs | 2 +- .../Screens/Sidebar/SidebarViewModel.cs | 119 ++++++++-------- src/Artemis.sln | 10 ++ .../DebugDeviceProvider.cs | 17 ++- .../GeneralModule.cs | 4 +- 21 files changed, 442 insertions(+), 122 deletions(-) create mode 100644 src/Artemis.Core/Services/Interfaces/IModuleService.cs create mode 100644 src/Artemis.Plugins.Modules.Overlay/Artemis - Backup.Plugins.Modules.Overlay.csproj create mode 100644 src/Artemis.Plugins.Modules.Overlay/Artemis.Plugins.Modules.Overlay.csproj create mode 100644 src/Artemis.Plugins.Modules.Overlay/OverlayModule.cs create mode 100644 src/Artemis.Plugins.Modules.Overlay/plugin.json diff --git a/src/Artemis.Core/Plugins/Modules/ActivationRequirements/ProcessActivationRequirement.cs b/src/Artemis.Core/Plugins/Modules/ActivationRequirements/ProcessActivationRequirement.cs index dda84aa6d..e18212b6d 100644 --- a/src/Artemis.Core/Plugins/Modules/ActivationRequirements/ProcessActivationRequirement.cs +++ b/src/Artemis.Core/Plugins/Modules/ActivationRequirements/ProcessActivationRequirement.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; using System.IO; using System.Linq; using Artemis.Core.Extensions; @@ -39,7 +40,7 @@ namespace Artemis.Core.Plugins.Modules.ActivationRequirements var processes = ProcessName != null ? Process.GetProcessesByName(ProcessName).Where(p => !p.HasExited) : Process.GetProcesses().Where(p => !p.HasExited); return Location != null - ? processes.Any(p => Path.GetDirectoryName(p.GetProcessFilename()) == Location) + ? processes.Any(p => string.Equals(Path.GetDirectoryName(p.GetProcessFilename()), Location, StringComparison.CurrentCultureIgnoreCase)) : processes.Any(); } } diff --git a/src/Artemis.Core/Plugins/Modules/Module.cs b/src/Artemis.Core/Plugins/Modules/Module.cs index eb71dbec3..7f77ca5f8 100644 --- a/src/Artemis.Core/Plugins/Modules/Module.cs +++ b/src/Artemis.Core/Plugins/Modules/Module.cs @@ -79,6 +79,12 @@ namespace Artemis.Core.Plugins.Modules /// public string DisplayIcon { get; set; } + /// + /// A path to an image to use as the modules display icon that's shown in the menu. + /// If set, takes precedence over + /// + public string DisplayIconPath { get; set; } + /// /// Gets whether this module is activated. A module can only be active while its /// are met @@ -138,14 +144,22 @@ namespace Artemis.Core.Plugins.Modules public abstract void Render(double deltaTime, ArtemisSurface surface, SKCanvas canvas, SKImageInfo canvasInfo); /// - /// Called when the are met + /// Called when the are met or during an override /// - public abstract void ModuleActivated(); + /// + /// If true, the activation was due to an override. This usually means the module was activated + /// by the profile editor + /// + public abstract void ModuleActivated(bool isOverride); /// - /// Called when the are no longer met + /// Called when the are no longer met or during an override /// - public abstract void ModuleDeactivated(); + /// + /// If true, the deactivation was due to an override. This usually means the module was deactivated + /// by the profile editor + /// + public abstract void ModuleDeactivated(bool isOverride); /// /// Evaluates the activation requirements following the and returns the result @@ -163,22 +177,22 @@ namespace Artemis.Core.Plugins.Modules return false; } - internal virtual void Activate() + internal virtual void Activate(bool isOverride) { if (IsActivated) return; - ModuleActivated(); + ModuleActivated(isOverride); IsActivated = true; } - internal virtual void Deactivate() + internal virtual void Deactivate(bool isOverride) { if (!IsActivated) return; IsActivated = false; - ModuleDeactivated(); + ModuleDeactivated(isOverride); } internal void ApplyToEntity() diff --git a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs index 894757b42..ad3b7c179 100644 --- a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs +++ b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs @@ -193,9 +193,9 @@ namespace Artemis.Core.Plugins.Modules /// public bool AnimatingProfileChange { get; private set; } - internal override void Deactivate() + internal override void Deactivate(bool isOverride) { - base.Deactivate(); + base.Deactivate(isOverride); var profile = ActiveProfile; ActiveProfile = null; diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index c4a223ec8..e6201f936 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -175,8 +175,8 @@ namespace Artemis.Core.Services lock (_modules) { modules = _modules.Where(m => m.IsActivated || m.InternalExpandsMainDataModel) - .OrderByDescending(m => m.PriorityCategory) - .ThenByDescending(m => m.Priority) + .OrderBy(m => m.PriorityCategory) + .ThenBy(m => m.Priority) .ToList(); } diff --git a/src/Artemis.Core/Services/Interfaces/IModuleService.cs b/src/Artemis.Core/Services/Interfaces/IModuleService.cs new file mode 100644 index 000000000..509c5048a --- /dev/null +++ b/src/Artemis.Core/Services/Interfaces/IModuleService.cs @@ -0,0 +1,41 @@ +using System.Threading.Tasks; +using Artemis.Core.Plugins.Modules; + +namespace Artemis.Core.Services.Interfaces +{ + /// + /// A service providing module activation functionality + /// + public interface IModuleService : IArtemisService + { + /// + /// Gets whether an override is currently being applied + /// + bool ApplyingOverride { get; } + + /// + /// Gets the current active module override. If set, all other modules are deactivated and only the + /// is active. + /// + Module ActiveModuleOverride { get; } + + /// + /// Changes the current and deactivates all other modules + /// + /// + Task SetActiveModuleOverride(Module overrideModule); + + /// + /// Evaluates every enabled module's activation requirements and activates/deactivates modules accordingly + /// + Task UpdateModuleActivation(); + + /// + /// Updates the priority and priority category of the given module + /// + /// The module to update + /// The new priority category of the module + /// The new priority of the module + void UpdateModulePriority(Module module, ModulePriorityCategory category, int priority); + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Interfaces/IPluginService.cs b/src/Artemis.Core/Services/Interfaces/IPluginService.cs index f806b5d37..76af14982 100644 --- a/src/Artemis.Core/Services/Interfaces/IPluginService.cs +++ b/src/Artemis.Core/Services/Interfaces/IPluginService.cs @@ -7,6 +7,9 @@ using RGB.NET.Core; namespace Artemis.Core.Services.Interfaces { + /// + /// A service providing plugin management + /// public interface IPluginService : IArtemisService, IDisposable { /// diff --git a/src/Artemis.Core/Services/ModuleService.cs b/src/Artemis.Core/Services/ModuleService.cs index e037fbf5d..f3f5eef53 100644 --- a/src/Artemis.Core/Services/ModuleService.cs +++ b/src/Artemis.Core/Services/ModuleService.cs @@ -1,5 +1,9 @@ -using System.Diagnostics; +using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using System.Timers; using Artemis.Core.Events; using Artemis.Core.Plugins.Modules; @@ -7,6 +11,7 @@ using Artemis.Core.Services.Interfaces; using Artemis.Core.Services.Storage.Interfaces; using Artemis.Storage.Repositories.Interfaces; using Serilog; +using Timer = System.Timers.Timer; namespace Artemis.Core.Services { @@ -26,58 +31,85 @@ namespace Artemis.Core.Services _pluginService.PluginEnabled += PluginServiceOnPluginEnabled; var activationUpdateTimer = new Timer(2000); - activationUpdateTimer.Elapsed += ActivationUpdateTimerOnElapsed; activationUpdateTimer.Start(); + activationUpdateTimer.Elapsed += ActivationUpdateTimerOnElapsed; + PopulatePriorities(); } - private void ActivationUpdateTimerOnElapsed(object sender, ElapsedEventArgs e) + public bool ApplyingOverride { get; private set; } + public Module ActiveModuleOverride { get; private set; } + + public async Task SetActiveModuleOverride(Module overrideModule) { - UpdateModuleActivation(); + if (ActiveModuleOverride == overrideModule) + return; + + try + { + // Not the cleanest way but locks don't work async and I cba with a mutex + while (ApplyingOverride) + await Task.Delay(50); + + ApplyingOverride = true; + ActiveModuleOverride = overrideModule; + + // If set to null, resume regular activation + if (ActiveModuleOverride == null) + { + await UpdateModuleActivation(); + _logger.Information("Cleared active module override"); + return; + } + + // If a module was provided, activate it and deactivate everything else + var modules = _pluginService.GetPluginsOfType().ToList(); + var deactivationTasks = new List(); + foreach (var module in modules) + { + if (module != ActiveModuleOverride) + deactivationTasks.Add(DeactivateModule(module, true)); + } + + await Task.WhenAll(deactivationTasks); + + if (!ActiveModuleOverride.IsActivated) + await ActivateModule(ActiveModuleOverride, true); + + _logger.Information($"Set active module override to {ActiveModuleOverride.DisplayName}"); + } + finally + { + ApplyingOverride = false; + } } - public void UpdateModuleActivation() + public async Task UpdateModuleActivation() { + if (ActiveModuleOverride != null) + return; + var stopwatch = new Stopwatch(); stopwatch.Start(); var modules = _pluginService.GetPluginsOfType().ToList(); + var tasks = new List(); foreach (var module in modules) { var shouldBeActivated = module.EvaluateActivationRequirements(); if (shouldBeActivated && !module.IsActivated) - { - module.Activate(); - // If this is a profile module, activate the last active profile after module activation - if (module is ProfileModule profileModule) - _profileService.ActivateLastProfile(profileModule); - } + tasks.Add(ActivateModule(module, false)); else if (!shouldBeActivated && module.IsActivated) - module.Deactivate(); + tasks.Add(DeactivateModule(module, false)); } + await Task.WhenAll(tasks); + stopwatch.Stop(); if (stopwatch.ElapsedMilliseconds > 100) _logger.Warning("Activation requirements evaluation took too long: {moduleCount} module(s) in {elapsed}", modules.Count, stopwatch.Elapsed); } - public void PopulatePriorities() - { - var modules = _pluginService.GetPluginsOfType().ToList(); - var moduleEntities = _moduleRepository.GetAll(); - - foreach (var module in modules) - { - var entity = moduleEntities.FirstOrDefault(e => e.PluginGuid == module.PluginInfo.Guid); - if (entity != null) - { - module.Entity = entity; - module.PriorityCategory = (ModulePriorityCategory) entity.PriorityCategory; - module.Priority = entity.Priority; - } - } - } - public void UpdateModulePriority(Module module, ModulePriorityCategory category, int priority) { var modules = _pluginService.GetPluginsOfType().Where(m => m.PriorityCategory == category).OrderBy(m => m.Priority).ToList(); @@ -105,6 +137,46 @@ namespace Artemis.Core.Services } } + private async void ActivationUpdateTimerOnElapsed(object sender, ElapsedEventArgs e) + { + await UpdateModuleActivation(); + } + + private async Task ActivateModule(Module module, bool isOverride) + { + module.Activate(isOverride); + + // If this is a profile module, activate the last active profile after module activation + if (module is ProfileModule profileModule) + await _profileService.ActivateLastProfileAnimated(profileModule); + } + + private async Task DeactivateModule(Module module, bool isOverride) + { + // If this is a profile module, activate the last active profile after module activation + if (module.IsActivated && module is ProfileModule profileModule) + await profileModule.ChangeActiveProfileAnimated(null, null); + + module.Deactivate(isOverride); + } + + private void PopulatePriorities() + { + var modules = _pluginService.GetPluginsOfType().ToList(); + var moduleEntities = _moduleRepository.GetAll(); + + foreach (var module in modules) + { + var entity = moduleEntities.FirstOrDefault(e => e.PluginGuid == module.PluginInfo.Guid); + if (entity != null) + { + module.Entity = entity; + module.PriorityCategory = (ModulePriorityCategory) entity.PriorityCategory; + module.Priority = entity.Priority; + } + } + } + private void PluginServiceOnPluginEnabled(object sender, PluginEventArgs e) { if (e.PluginInfo.Instance is Module module) @@ -124,8 +196,4 @@ namespace Artemis.Core.Services UpdateModulePriority(module, module.DefaultPriorityCategory, 1); } } - - public interface IModuleService : IArtemisService - { - } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs index 5fea023cb..00e0ddfed 100644 --- a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs +++ b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs @@ -51,6 +51,14 @@ namespace Artemis.Core.Services.Storage.Interfaces /// void ActivateLastProfile(ProfileModule profileModule); + /// + /// Asynchronously activates the last profile of the given profile module using a fade animation + /// + /// + /// + + Task ActivateLastProfileAnimated(ProfileModule profileModule); + /// /// Activates the profile described in the given with the currently active surface /// diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 964eb9266..88d6c5a12 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -64,6 +64,13 @@ namespace Artemis.Core.Services.Storage ActivateProfile(activeProfile); } + public async Task ActivateLastProfileAnimated(ProfileModule profileModule) + { + var activeProfile = GetLastActiveProfile(profileModule); + if (activeProfile != null) + await ActivateProfileAnimated(activeProfile); + } + public Profile ActivateProfile(ProfileDescriptor profileDescriptor) { if (profileDescriptor.ProfileModule.ActiveProfile?.EntityId == profileDescriptor.Id) @@ -94,12 +101,25 @@ namespace Artemis.Core.Services.Storage var profile = new Profile(profileDescriptor.ProfileModule, profileEntity); InstantiateProfile(profile); + void ActivatingProfileSurfaceUpdate(object sender, SurfaceConfigurationEventArgs e) => profile.PopulateLeds(e.Surface); + void ActivatingProfilePluginToggle(object sender, PluginEventArgs e) => InstantiateProfile(profile); + + // This could happen during activation so subscribe to it + _pluginService.PluginEnabled += ActivatingProfilePluginToggle; + _pluginService.PluginDisabled += ActivatingProfilePluginToggle; + _surfaceService.SurfaceConfigurationUpdated += ActivatingProfileSurfaceUpdate; + await profileDescriptor.ProfileModule.ChangeActiveProfileAnimated(profile, _surfaceService.ActiveSurface); SaveActiveProfile(profileDescriptor.ProfileModule); + _pluginService.PluginEnabled -= ActivatingProfilePluginToggle; + _pluginService.PluginDisabled -= ActivatingProfilePluginToggle; + _surfaceService.SurfaceConfigurationUpdated -= ActivatingProfileSurfaceUpdate; + return profile; } + public void ClearActiveProfile(ProfileModule module) { module.ChangeActiveProfile(null, _surfaceService.ActiveSurface); @@ -234,6 +254,9 @@ namespace Artemis.Core.Services.Storage private void SaveActiveProfile(ProfileModule module) { + if (module.ActiveProfile == null) + return; + var profileEntities = _profileRepository.GetByPluginGuid(module.PluginInfo.Guid); foreach (var profileEntity in profileEntities) { diff --git a/src/Artemis.Core/Utilities/IntroAnimation.cs b/src/Artemis.Core/Utilities/IntroAnimation.cs index 06a6ba79c..02a5c59a6 100644 --- a/src/Artemis.Core/Utilities/IntroAnimation.cs +++ b/src/Artemis.Core/Utilities/IntroAnimation.cs @@ -79,12 +79,12 @@ namespace Artemis.Core.Utilities throw new NotImplementedException(); } - public override void ModuleActivated() + public override void ModuleActivated(bool isOverride) { throw new NotImplementedException(); } - public override void ModuleDeactivated() + public override void ModuleDeactivated(bool isOverride) { throw new NotImplementedException(); } diff --git a/src/Artemis.Plugins.Modules.Overlay/Artemis - Backup.Plugins.Modules.Overlay.csproj b/src/Artemis.Plugins.Modules.Overlay/Artemis - Backup.Plugins.Modules.Overlay.csproj new file mode 100644 index 000000000..358044031 --- /dev/null +++ b/src/Artemis.Plugins.Modules.Overlay/Artemis - Backup.Plugins.Modules.Overlay.csproj @@ -0,0 +1,46 @@ + + + netcoreapp3.1 + + Artemis.Plugins.Modules.Overlay + Artemis.Plugins.Modules.Overlay + x64 + + + x64 + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + PreserveNewest + + + + + + \ No newline at end of file diff --git a/src/Artemis.Plugins.Modules.Overlay/Artemis.Plugins.Modules.Overlay.csproj b/src/Artemis.Plugins.Modules.Overlay/Artemis.Plugins.Modules.Overlay.csproj new file mode 100644 index 000000000..2b5876ee1 --- /dev/null +++ b/src/Artemis.Plugins.Modules.Overlay/Artemis.Plugins.Modules.Overlay.csproj @@ -0,0 +1,35 @@ + + + netcoreapp3.1 + + Artemis.Plugins.Modules.Overlay + Artemis.Plugins.Modules.Overlay + x64 + + + x64 + + + + + + + + + + + + + + + PreserveNewest + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.Plugins.Modules.Overlay/OverlayModule.cs b/src/Artemis.Plugins.Modules.Overlay/OverlayModule.cs new file mode 100644 index 000000000..d079c1626 --- /dev/null +++ b/src/Artemis.Plugins.Modules.Overlay/OverlayModule.cs @@ -0,0 +1,35 @@ +using Artemis.Core.Plugins.Modules; +using Artemis.Core.Plugins.Modules.ActivationRequirements; + +namespace Artemis.Plugins.Modules.Overlay +{ + // The core of your module. Hover over the method names to see a description. + public class OverlayModule : ProfileModule + { + // This is the beginning of your plugin life cycle. Use this instead of a constructor. + public override void EnablePlugin() + { + DisplayName = "Overlay"; + DisplayIcon = "ArrangeBringToFront"; + DefaultPriorityCategory = ModulePriorityCategory.Overlay; + + ActivationRequirements.Add(new ProcessActivationRequirement("taskmgr")); + } + + // This is the end of your plugin life cycle. + public override void DisablePlugin() + { + // Make sure to clean up resources where needed (dispose IDisposables etc.) + } + + public override void ModuleActivated(bool isOverride) + { + // When this gets called your activation requirements have been met and the module will start displaying + } + + public override void ModuleDeactivated(bool isOverride) + { + // When this gets called your activation requirements are no longer met and your module will stop displaying + } + } +} \ No newline at end of file diff --git a/src/Artemis.Plugins.Modules.Overlay/plugin.json b/src/Artemis.Plugins.Modules.Overlay/plugin.json new file mode 100644 index 000000000..bbb0b9206 --- /dev/null +++ b/src/Artemis.Plugins.Modules.Overlay/plugin.json @@ -0,0 +1,7 @@ +{ + "Guid": "29e3ff97-83a5-44fc-a2dc-04f446b54146", + "Name": "Overlay module", + "Description": "A general profile-enabled overlay module for every-day use", + "Version": "1.0.0.0", + "Main": "Artemis.Plugins.Modules.Overlay.dll" +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ModuleRootViewModel.cs b/src/Artemis.UI/Screens/Module/ModuleRootViewModel.cs index 0ac585fe6..7851a2982 100644 --- a/src/Artemis.UI/Screens/Module/ModuleRootViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ModuleRootViewModel.cs @@ -2,6 +2,8 @@ using System.Linq; using System.Threading.Tasks; using Artemis.Core.Plugins.Modules; +using Artemis.Core.Services; +using Artemis.Core.Services.Interfaces; using Artemis.UI.Ninject.Factories; using Ninject; using Ninject.Parameters; @@ -11,22 +13,41 @@ namespace Artemis.UI.Screens.Module { public class ModuleRootViewModel : Conductor.Collection.OneActive { + private readonly IModuleService _moduleService; private readonly IProfileEditorVmFactory _profileEditorVmFactory; private readonly IKernel _kernel; - public ModuleRootViewModel(Core.Plugins.Modules.Module module, IProfileEditorVmFactory profileEditorVmFactory, IKernel kernel) + public ModuleRootViewModel(Core.Plugins.Modules.Module module, IModuleService moduleService, IProfileEditorVmFactory profileEditorVmFactory, IKernel kernel) { DisplayName = module?.DisplayName; Module = module; + _moduleService = moduleService; _profileEditorVmFactory = profileEditorVmFactory; _kernel = kernel; - - Task.Run(AddTabsAsync); } public Core.Plugins.Modules.Module Module { get; } + protected override void OnActivate() + { + Task.Run(async () => + { + await _moduleService.SetActiveModuleOverride(Module); + await AddTabsAsync(); + }); + base.OnActivate(); + } + + protected override void OnDeactivate() + { + Task.Run(async () => + { + await _moduleService.SetActiveModuleOverride(null); + }); + base.OnDeactivate(); + } + private async Task AddTabsAsync() { // Give the screen a moment to active without freezing the UI thread diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs index a4d803ee7..c304aba1e 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs @@ -228,14 +228,14 @@ namespace Artemis.UI.Screens.ProfileEditor _snackbarMessageQueue.Enqueue("Redid profile update", "UNDO", Undo); } - protected override void OnInitialActivate() + protected override void OnActivate() { LoadWorkspaceSettings(); Module.IsProfileUpdatingDisabled = true; Module.ActiveProfileChanged += ModuleOnActiveProfileChanged; Execute.PostToUIThread(LoadProfiles); - base.OnInitialActivate(); + base.OnActivate(); } protected override void OnClose() @@ -243,6 +243,8 @@ namespace Artemis.UI.Screens.ProfileEditor SaveWorkspaceSettings(); Module.IsProfileUpdatingDisabled = false; Module.ActiveProfileChanged -= ModuleOnActiveProfileChanged; + + _profileEditorService.ChangeSelectedProfile(null); base.OnClose(); } diff --git a/src/Artemis.UI/Screens/RootViewModel.cs b/src/Artemis.UI/Screens/RootViewModel.cs index 830237a8a..c6c29a490 100644 --- a/src/Artemis.UI/Screens/RootViewModel.cs +++ b/src/Artemis.UI/Screens/RootViewModel.cs @@ -137,7 +137,7 @@ namespace Artemis.UI.Screens private void SidebarViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(SidebarViewModel.SelectedItem)) + if (e.PropertyName == nameof(SidebarViewModel.SelectedItem) && ActiveItem != SidebarViewModel.SelectedItem) { SidebarViewModel.IsSidebarOpen = false; ActiveItemReady = false; diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs index 8bc5fff76..87d6e5779 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs +++ b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading.Tasks; using System.Timers; +using System.Windows.Media.Imaging; using Artemis.Core.Events; using Artemis.Core.Services.Interfaces; using Artemis.UI.Events; @@ -22,15 +24,15 @@ namespace Artemis.UI.Screens.Sidebar { public class SidebarViewModel : PropertyChangedBase, IHandle, IDisposable { + private readonly Timer _activeModulesUpdateTimer; private readonly IKernel _kernel; private readonly IModuleVmFactory _moduleVmFactory; private readonly IPluginService _pluginService; + private string _activeModules; + private bool _isSidebarOpen; + private IScreen _selectedItem; private BindableCollection _sidebarItems; private Dictionary _sidebarModules; - private IScreen _selectedItem; - private bool _isSidebarOpen; - private readonly Timer _activeModulesUpdateTimer; - private string _activeModules; public SidebarViewModel(IKernel kernel, IEventAggregator eventAggregator, IModuleVmFactory moduleVmFactory, IPluginService pluginService) { @@ -52,15 +54,6 @@ namespace Artemis.UI.Screens.Sidebar eventAggregator.Subscribe(this); } - private void ActiveModulesUpdateTimerOnElapsed(object sender, EventArgs e) - { - if (!IsSidebarOpen) - return; - - var activeModules = SidebarModules.Count(m => m.Value.IsActivated); - ActiveModules = activeModules == 1 ? "1 active module" : $"{activeModules} active modules"; - } - public BindableCollection SidebarItems { get => _sidebarItems; @@ -96,6 +89,18 @@ namespace Artemis.UI.Screens.Sidebar } } + public void Dispose() + { + SelectedItem?.Deactivate(); + SelectedItem = null; + + _pluginService.PluginEnabled -= PluginServiceOnPluginEnabled; + _pluginService.PluginDisabled -= PluginServiceOnPluginDisabled; + + _activeModulesUpdateTimer.Stop(); + _activeModulesUpdateTimer.Elapsed -= ActiveModulesUpdateTimerOnElapsed; + } + public void SetupSidebar() { SidebarItems.Clear(); @@ -120,7 +125,7 @@ namespace Artemis.UI.Screens.Sidebar } // ReSharper disable once UnusedMember.Global - Called by view - public async Task SelectItem(WillSelectNavigationItemEventArgs args) + public void SelectItem(WillSelectNavigationItemEventArgs args) { if (args.NavigationItemToSelect == null) { @@ -128,7 +133,7 @@ namespace Artemis.UI.Screens.Sidebar return; } - await SelectSidebarItem(args.NavigationItemToSelect); + SelectSidebarItem(args.NavigationItemToSelect); } public void AddModule(Core.Plugins.Modules.Module module) @@ -137,11 +142,19 @@ namespace Artemis.UI.Screens.Sidebar if (SidebarModules.Any(io => io.Value == module)) return; - // Icon is provided as string to avoid having to reference MaterialDesignThemes - var parsedIcon = Enum.TryParse(module.DisplayIcon, true, out var iconEnum); - if (parsedIcon == false) - iconEnum = PackIconKind.QuestionMarkCircle; - var sidebarItem = new FirstLevelNavigationItem {Icon = iconEnum, Label = module.DisplayName}; + object icon; + if (module.DisplayIconPath != null && File.Exists(Path.Combine(module.PluginInfo.Directory.FullName, module.DisplayIconPath))) + icon = new BitmapImage(new Uri(Path.Combine(module.PluginInfo.Directory.FullName, module.DisplayIconPath))); + else + { + // Icon is provided as string to avoid having to reference MaterialDesignThemes + var parsedIcon = Enum.TryParse(module.DisplayIcon, true, out var iconEnum); + if (parsedIcon == false) + iconEnum = PackIconKind.QuestionMarkCircle; + icon = iconEnum; + } + + var sidebarItem = new FirstLevelNavigationItem {Icon = icon, Label = module.DisplayName}; SidebarItems.Add(sidebarItem); SidebarModules.Add(sidebarItem, module); } @@ -157,54 +170,48 @@ namespace Artemis.UI.Screens.Sidebar SidebarModules.Remove(existing.Key); } - private async Task SelectSidebarItem(INavigationItem sidebarItem) + private void ActiveModulesUpdateTimerOnElapsed(object sender, EventArgs e) + { + if (!IsSidebarOpen) + return; + + var activeModules = SidebarModules.Count(m => m.Value.IsActivated); + ActiveModules = activeModules == 1 ? "1 active module" : $"{activeModules} active modules"; + } + + private void SelectSidebarItem(INavigationItem sidebarItem) { // A module was selected if the dictionary contains the selected item if (SidebarModules.ContainsKey(sidebarItem)) - await ActivateModule(sidebarItem); + ActivateModule(sidebarItem); else if (sidebarItem is FirstLevelNavigationItem navigationItem) - await ActivateViewModel(navigationItem.Label); - else if (await CloseCurrentItem()) + ActivateViewModel(navigationItem.Label); + else 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) + private void ActivateViewModel(string label) { if (label == "Home") - await ActivateViewModel(); + ActivateViewModel(); else if (label == "News") - await ActivateViewModel(); + ActivateViewModel(); else if (label == "Workshop") - await ActivateViewModel(); + ActivateViewModel(); else if (label == "Surface Editor") - await ActivateViewModel(); + ActivateViewModel(); else if (label == "Settings") - await ActivateViewModel(); + ActivateViewModel(); } - private async Task ActivateViewModel() + private void ActivateViewModel() { - if (await CloseCurrentItem()) - SelectedItem = (IScreen) _kernel.Get(); + SelectedItem = (IScreen) _kernel.Get(); } - private async Task ActivateModule(INavigationItem sidebarItem) + private void ActivateModule(INavigationItem sidebarItem) { - if (await CloseCurrentItem()) - SelectedItem = SidebarModules.ContainsKey(sidebarItem) ? _moduleVmFactory.Create(SidebarModules[sidebarItem]) : null; + SelectedItem = SidebarModules.ContainsKey(sidebarItem) ? _moduleVmFactory.Create(SidebarModules[sidebarItem]) : null; } #region Event handlers @@ -223,21 +230,9 @@ namespace Artemis.UI.Screens.Sidebar public void Handle(RequestSelectSidebarItemEvent message) { - Execute.OnUIThread(async () => await ActivateViewModel(message.Label)); + ActivateViewModel(message.Label); } #endregion - - public void Dispose() - { - var closeTask = CloseCurrentItem(); - closeTask.Wait(); - - _pluginService.PluginEnabled -= PluginServiceOnPluginEnabled; - _pluginService.PluginDisabled -= PluginServiceOnPluginDisabled; - - _activeModulesUpdateTimer.Stop(); - _activeModulesUpdateTimer.Elapsed -= ActiveModulesUpdateTimerOnElapsed; - } } } \ No newline at end of file diff --git a/src/Artemis.sln b/src/Artemis.sln index 1af8e3f62..889050ad5 100644 --- a/src/Artemis.sln +++ b/src/Artemis.sln @@ -10,6 +10,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.UI", "Artemis.UI\Ar {3D83760B-0A36-4C8F-978D-7949C3FC862B} = {3D83760B-0A36-4C8F-978D-7949C3FC862B} {8DC7960F-6DDF-4007-A155-17E124F39374} = {8DC7960F-6DDF-4007-A155-17E124F39374} {DCF7C321-95DC-4507-BB61-A7C5356E58EC} = {DCF7C321-95DC-4507-BB61-A7C5356E58EC} + {00318027-7FDB-4C86-AB86-9005A481E330} = {00318027-7FDB-4C86-AB86-9005A481E330} {E592F239-FAA0-4840-9C85-46E5867D06D5} = {E592F239-FAA0-4840-9C85-46E5867D06D5} {36C10640-A31F-4DEE-9F0E-9B9E3F12753D} = {36C10640-A31F-4DEE-9F0E-9B9E3F12753D} {62214042-667E-4B29-B64E-1A68CE6FE209} = {62214042-667E-4B29-B64E-1A68CE6FE209} @@ -76,6 +77,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.Plugins.LayerEffect EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.Plugins.Devices.Debug", "Plugins\Artemis.Plugins.Devices.Debug\Artemis.Plugins.Devices.Debug.csproj", "{3D83760B-0A36-4C8F-978D-7949C3FC862B}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.Plugins.Modules.Overlay", "Artemis.Plugins.Modules.Overlay\Artemis.Plugins.Modules.Overlay.csproj", "{00318027-7FDB-4C86-AB86-9005A481E330}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -260,6 +263,12 @@ Global {3D83760B-0A36-4C8F-978D-7949C3FC862B}.Release|Any CPU.Build.0 = Release|Any CPU {3D83760B-0A36-4C8F-978D-7949C3FC862B}.Release|x64.ActiveCfg = Release|Any CPU {3D83760B-0A36-4C8F-978D-7949C3FC862B}.Release|x64.Build.0 = Release|Any CPU + {00318027-7FDB-4C86-AB86-9005A481E330}.Debug|Any CPU.ActiveCfg = Debug|x64 + {00318027-7FDB-4C86-AB86-9005A481E330}.Debug|x64.ActiveCfg = Debug|x64 + {00318027-7FDB-4C86-AB86-9005A481E330}.Debug|x64.Build.0 = Debug|x64 + {00318027-7FDB-4C86-AB86-9005A481E330}.Release|Any CPU.ActiveCfg = Release|x64 + {00318027-7FDB-4C86-AB86-9005A481E330}.Release|x64.ActiveCfg = Release|x64 + {00318027-7FDB-4C86-AB86-9005A481E330}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -287,6 +296,7 @@ Global {2C1477DC-7A5C-4B65-85DB-1F16A18FB2EC} = {E830A02B-A7E5-4A6B-943F-76B0A542630C} {62214042-667E-4B29-B64E-1A68CE6FE209} = {2C1477DC-7A5C-4B65-85DB-1F16A18FB2EC} {3D83760B-0A36-4C8F-978D-7949C3FC862B} = {88792A7E-F037-4280-81D3-B131508EF1D8} + {00318027-7FDB-4C86-AB86-9005A481E330} = {B258A061-FA19-4835-8DC4-E9C3AE3664A0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C203080A-4473-4CC2-844B-F552EA43D66A} diff --git a/src/Plugins/Artemis.Plugins.Devices.Debug/DebugDeviceProvider.cs b/src/Plugins/Artemis.Plugins.Devices.Debug/DebugDeviceProvider.cs index 8fb860c76..bc0ea9b4a 100644 --- a/src/Plugins/Artemis.Plugins.Devices.Debug/DebugDeviceProvider.cs +++ b/src/Plugins/Artemis.Plugins.Devices.Debug/DebugDeviceProvider.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using Artemis.Core.Plugins; using Artemis.Core.Plugins.DeviceProviders; @@ -8,6 +9,7 @@ using Artemis.Plugins.Devices.Debug.Settings; using Artemis.Plugins.Devices.Debug.ViewModels; using RGB.NET.Core; using RGB.NET.Devices.Debug; +using Serilog; namespace Artemis.Plugins.Devices.Debug { @@ -16,10 +18,12 @@ namespace Artemis.Plugins.Devices.Debug { private readonly IRgbService _rgbService; private readonly PluginSettings _settings; + private readonly ILogger _logger; - public DebugDeviceProvider(IRgbService rgbService, PluginSettings settings) : base(RGB.NET.Devices.Debug.DebugDeviceProvider.Instance) + public DebugDeviceProvider(IRgbService rgbService, PluginSettings settings, ILogger logger) : base(RGB.NET.Devices.Debug.DebugDeviceProvider.Instance) { _settings = settings; + _logger = logger; _rgbService = rgbService; } @@ -35,7 +39,14 @@ namespace Artemis.Plugins.Devices.Debug foreach (var deviceDefinition in definitions.Value) RGB.NET.Devices.Debug.DebugDeviceProvider.Instance.AddFakeDeviceDefinition(deviceDefinition.Layout, deviceDefinition.ImageLayout); - _rgbService.AddDeviceProvider(RgbDeviceProvider); + try + { + _rgbService.AddDeviceProvider(RgbDeviceProvider); + } + catch (Exception e) + { + _logger.Warning(e, "Debug device provided failed to initialize, check paths"); + } } public override void DisablePlugin() diff --git a/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs b/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs index 60cb9b2dd..d8759b149 100644 --- a/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs +++ b/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs @@ -28,11 +28,11 @@ namespace Artemis.Plugins.Modules.General { } - public override void ModuleActivated() + public override void ModuleActivated(bool isOverride) { } - public override void ModuleDeactivated() + public override void ModuleDeactivated(bool isOverride) { }