From d5d1211649013f6420856f9dbab6ca6332dc312d Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 10 Mar 2021 20:53:37 +0100 Subject: [PATCH 1/5] Plugins - Load features by their info before enabling plugins General - Added missing XML comments --- .../Events/Plugins/PluginFeatureEventArgs.cs | 16 +++ .../Conditions/DataModelConditionEvent.cs | 8 +- src/Artemis.Core/Models/Profile/Layer.cs | 4 +- .../Models/Surface/ArtemisDevice.cs | 9 +- src/Artemis.Core/Models/Surface/ArtemisLed.cs | 5 +- .../Models/Surface/Layout/ArtemisLedLayout.cs | 3 + .../Plugins/LayerBrushes/RgbNetLayerBrush.cs | 61 ++++++------ src/Artemis.Core/Plugins/Plugin.cs | 49 +++++----- src/Artemis.Core/Plugins/PluginFeatureInfo.cs | 59 ++++++++--- .../Services/Interfaces/IRgbService.cs | 13 ++- src/Artemis.Core/Services/ModuleService.cs | 4 +- .../Services/PluginManagementService.cs | 98 ++++++++++--------- .../Services/Storage/ProfileService.cs | 3 +- .../Utilities/CorePluginFeature.cs | 2 +- src/Artemis.Core/Utilities/IntroAnimation.cs | 2 +- .../Ninject/Factories/IVMFactory.cs | 2 +- .../DisplayConditionsViewModel.cs | 2 +- .../DataBindings/DataBindingsViewModel.cs | 5 +- .../Tabs/Plugins/PluginFeatureView.xaml | 6 +- .../Tabs/Plugins/PluginFeatureViewModel.cs | 24 ++--- .../Tabs/Plugins/PluginSettingsView.xaml | 12 +-- .../Tabs/Plugins/PluginSettingsViewModel.cs | 26 +---- .../Steps/DevicesStepViewModel.cs | 6 +- .../Services/DeviceLayoutService.cs | 2 +- 24 files changed, 241 insertions(+), 180 deletions(-) diff --git a/src/Artemis.Core/Events/Plugins/PluginFeatureEventArgs.cs b/src/Artemis.Core/Events/Plugins/PluginFeatureEventArgs.cs index 8d33d0f4b..d16789b82 100644 --- a/src/Artemis.Core/Events/Plugins/PluginFeatureEventArgs.cs +++ b/src/Artemis.Core/Events/Plugins/PluginFeatureEventArgs.cs @@ -17,4 +17,20 @@ namespace Artemis.Core /// public PluginFeature PluginFeature { get; } } + + /// + /// Provides data about plugin feature info related events + /// + public class PluginFeatureInfoEventArgs : EventArgs + { + internal PluginFeatureInfoEventArgs(PluginFeatureInfo pluginFeatureInfo) + { + PluginFeatureInfo = pluginFeatureInfo; + } + + /// + /// Gets the plugin feature this event is related to + /// + public PluginFeatureInfo PluginFeatureInfo { get; } + } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs index 64529c531..dd849741c 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionEvent.cs @@ -38,6 +38,9 @@ namespace Artemis.Core /// public DataModelPath? EventPath { get; private set; } + /// + /// Gets the last time the event this condition is applied to was triggered + /// public DateTime LastTrigger { get; private set; } /// @@ -53,7 +56,7 @@ namespace Artemis.Core if (_disposed) throw new ObjectDisposedException("DataModelConditionEvent"); - if (EventPath?.GetValue() is not IDataModelEvent dataModelEvent) + if (EventPath?.GetValue() is not IDataModelEvent dataModelEvent) return false; // Only evaluate to true once every time the event has been triggered since the last evaluation if (dataModelEvent.LastTrigger <= LastTrigger) @@ -67,7 +70,6 @@ namespace Artemis.Core // If there are no children, we always evaluate to true whenever the event triggered return true; - } /// @@ -131,7 +133,7 @@ namespace Artemis.Core // Target list EventPath?.Save(); Entity.EventPath = EventPath?.Entity; - + // Children Entity.Children.Clear(); Entity.Children.AddRange(Children.Select(c => c.GetEntity())); diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 9a98dfcc2..31d8d8ae4 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -225,7 +225,7 @@ namespace Artemis.Core { LedEntity ledEntity = new() { - DeviceIdentifier = artemisLed.Device.RgbDevice.GetDeviceIdentifier(), + DeviceIdentifier = artemisLed.Device.Identifier, LedName = artemisLed.RgbLed.Id.ToString() }; LayerEntity.Leds.Add(ledEntity); @@ -578,7 +578,7 @@ namespace Artemis.Core List availableLeds = devices.SelectMany(d => d.Leds).ToList(); foreach (LedEntity ledEntity in LayerEntity.Leds) { - ArtemisLed? match = availableLeds.FirstOrDefault(a => a.Device.RgbDevice.GetDeviceIdentifier() == ledEntity.DeviceIdentifier && + ArtemisLed? match = availableLeds.FirstOrDefault(a => a.Device.Identifier == ledEntity.DeviceIdentifier && a.RgbLed.Id.ToString() == ledEntity.LedName); if (match != null) leds.Add(match); diff --git a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs index 9146097e2..cee4d8dfd 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs @@ -21,6 +21,7 @@ namespace Artemis.Core internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider) { + Identifier = rgbDevice.GetDeviceIdentifier(); DeviceEntity = new DeviceEntity(); RgbDevice = rgbDevice; DeviceProvider = deviceProvider; @@ -45,6 +46,7 @@ namespace Artemis.Core internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider, DeviceEntity deviceEntity) { + Identifier = rgbDevice.GetDeviceIdentifier(); DeviceEntity = deviceEntity; RgbDevice = rgbDevice; DeviceProvider = deviceProvider; @@ -59,6 +61,11 @@ namespace Artemis.Core ApplyKeyboardLayout(); } + /// + /// Gets the (hopefully unique and persistent) ID of this device + /// + public string Identifier { get; } + /// /// Gets the rectangle covering the device /// @@ -336,7 +343,7 @@ namespace Artemis.Core internal void ApplyToEntity() { // Other properties are computed - DeviceEntity.Id = RgbDevice.GetDeviceIdentifier(); + DeviceEntity.Id = Identifier; DeviceEntity.InputIdentifiers.Clear(); foreach (ArtemisDeviceInputIdentifier identifier in InputIdentifiers) diff --git a/src/Artemis.Core/Models/Surface/ArtemisLed.cs b/src/Artemis.Core/Models/Surface/ArtemisLed.cs index 473757f76..b8b81ec5b 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisLed.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisLed.cs @@ -46,6 +46,9 @@ namespace Artemis.Core private set => SetAndNotify(ref _absoluteRectangle, value); } + /// + /// Gets the layout applied to this LED + /// public ArtemisLedLayout? Layout { get; internal set; } /// @@ -53,7 +56,7 @@ namespace Artemis.Core { return RgbLed.ToString(); } - + internal void CalculateRectangles() { Rectangle = RgbLed.Boundary.ToSKRect(); diff --git a/src/Artemis.Core/Models/Surface/Layout/ArtemisLedLayout.cs b/src/Artemis.Core/Models/Surface/Layout/ArtemisLedLayout.cs index e0ee9fa07..838c62881 100644 --- a/src/Artemis.Core/Models/Surface/Layout/ArtemisLedLayout.cs +++ b/src/Artemis.Core/Models/Surface/Layout/ArtemisLedLayout.cs @@ -5,6 +5,9 @@ using RGB.NET.Layout; namespace Artemis.Core { + /// + /// Represents a LED layout decorated with extra Artemis-specific data + /// public class ArtemisLedLayout { internal ArtemisLedLayout(ArtemisLayout deviceLayout, ILedLayout led) diff --git a/src/Artemis.Core/Plugins/LayerBrushes/RgbNetLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/RgbNetLayerBrush.cs index 843321c99..7dd558a16 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/RgbNetLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/RgbNetLayerBrush.cs @@ -29,6 +29,9 @@ namespace Artemis.Core.LayerBrushes /// public ListLedGroup? LedGroup { get; internal set; } + /// + /// For internal use only, is public for dependency injection but ignore pl0x + /// [Inject] public IRgbService? RgbService { get; set; } @@ -38,35 +41,6 @@ namespace Artemis.Core.LayerBrushes /// Your RGB.NET effect public abstract IBrush GetBrush(); - internal void UpdateLedGroup() - { - if (LedGroup == null) - return; - - if (Layer.Parent != null) - LedGroup.ZIndex = Layer.Parent.Children.Count - Layer.Parent.Children.IndexOf(Layer); - else - LedGroup.ZIndex = 1; - - List missingLeds = Layer.Leds.Where(l => !LedGroup.ContainsLed(l.RgbLed)).Select(l => l.RgbLed).ToList(); - List extraLeds = LedGroup.Where(l => Layer.Leds.All(layerLed => layerLed.RgbLed != l)).ToList(); - LedGroup.AddLeds(missingLeds); - LedGroup.RemoveLeds(extraLeds); - LedGroup.Brush = GetBrush(); - } - - internal override void Initialize() - { - if (RgbService == null) - throw new ArtemisCoreException("Cannot initialize RGB.NET layer brush because RgbService is not set"); - - LedGroup = new ListLedGroup(RgbService.Surface); - Layer.RenderPropertiesUpdated += LayerOnRenderPropertiesUpdated; - - InitializeProperties(); - UpdateLedGroup(); - } - #region IDisposable /// @@ -87,6 +61,35 @@ namespace Artemis.Core.LayerBrushes #endregion + internal void UpdateLedGroup() + { + if (LedGroup == null) + return; + + if (Layer.Parent != null) + LedGroup.ZIndex = Layer.Parent.Children.Count - Layer.Parent.Children.IndexOf(Layer); + else + LedGroup.ZIndex = 1; + + List missingLeds = Layer.Leds.Where(l => !LedGroup.ContainsLed(l.RgbLed)).Select(l => l.RgbLed).ToList(); + List extraLeds = LedGroup.Where(l => Layer.Leds.All(layerLed => layerLed.RgbLed != l)).ToList(); + LedGroup.AddLeds(missingLeds); + LedGroup.RemoveLeds(extraLeds); + LedGroup.Brush = GetBrush(); + } + + internal override void Initialize() + { + if (RgbService == null) + throw new ArtemisCoreException("Cannot initialize RGB.NET layer brush because RgbService is not set"); + + LedGroup = new ListLedGroup(RgbService.Surface); + Layer.RenderPropertiesUpdated += LayerOnRenderPropertiesUpdated; + + InitializeProperties(); + UpdateLedGroup(); + } + // Not used in this effect type internal override void InternalRender(SKCanvas canvas, SKRect bounds, SKPaint paint) { diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs index 53c581e7c..3ab1a99ef 100644 --- a/src/Artemis.Core/Plugins/Plugin.cs +++ b/src/Artemis.Core/Plugins/Plugin.cs @@ -16,7 +16,7 @@ namespace Artemis.Core /// public class Plugin : CorePropertyChanged, IDisposable { - private readonly List _features; + private readonly List _features; private bool _isEnabled; @@ -26,7 +26,7 @@ namespace Artemis.Core Directory = directory; Entity = pluginEntity ?? new PluginEntity {Id = Guid, IsEnabled = true}; - _features = new List(); + _features = new List(); } /// @@ -61,7 +61,7 @@ namespace Artemis.Core /// /// Gets a read-only collection of all features this plugin provides /// - public ReadOnlyCollection Features => _features.AsReadOnly(); + public ReadOnlyCollection Features => _features.AsReadOnly(); /// /// The assembly the plugin code lives in @@ -107,7 +107,7 @@ namespace Artemis.Core /// If found, the instance of the feature public T? GetFeature() where T : PluginFeature { - return _features.FirstOrDefault(i => i is T) as T; + return _features.FirstOrDefault(i => i.Instance is T) as T; } /// @@ -122,31 +122,32 @@ namespace Artemis.Core Entity.IsEnabled = IsEnabled; } - internal void AddFeature(PluginFeature feature) + internal void AddFeature(PluginFeatureInfo featureInfo) { - feature.Plugin = this; - _features.Add(feature); + if (featureInfo.Plugin != this) + throw new ArtemisCoreException("Feature is not associated with this plugin"); + _features.Add(featureInfo); - OnFeatureAdded(new PluginFeatureEventArgs(feature)); + OnFeatureAdded(new PluginFeatureInfoEventArgs(featureInfo)); } - internal void RemoveFeature(PluginFeature feature) + internal void RemoveFeature(PluginFeatureInfo featureInfo) { - if (feature.IsEnabled) + if (featureInfo.Instance != null && featureInfo.Instance.IsEnabled) throw new ArtemisCoreException("Cannot remove an enabled feature from a plugin"); - - _features.Remove(feature); - feature.Dispose(); - OnFeatureRemoved(new PluginFeatureEventArgs(feature)); + _features.Remove(featureInfo); + featureInfo.Instance?.Dispose(); + + OnFeatureRemoved(new PluginFeatureInfoEventArgs(featureInfo)); } - + internal void SetEnabled(bool enable) { if (IsEnabled == enable) return; - if (!enable && Features.Any(e => e.IsEnabled)) + if (!enable && Features.Any(e => e.Instance != null && e.Instance.IsEnabled)) throw new ArtemisCoreException("Cannot disable this plugin because it still has enabled features"); IsEnabled = enable; @@ -176,8 +177,8 @@ namespace Artemis.Core { if (disposing) { - foreach (PluginFeature feature in Features) - feature.Dispose(); + foreach (PluginFeatureInfo feature in Features) + feature.Instance?.Dispose(); SetEnabled(false); Kernel?.Dispose(); @@ -189,7 +190,7 @@ namespace Artemis.Core _features.Clear(); } } - + /// public void Dispose() { @@ -198,7 +199,7 @@ namespace Artemis.Core } #endregion - + #region Events /// @@ -214,12 +215,12 @@ namespace Artemis.Core /// /// Occurs when an feature is loaded and added to the plugin /// - public event EventHandler? FeatureAdded; + public event EventHandler? FeatureAdded; /// /// Occurs when an feature is disabled and removed from the plugin /// - public event EventHandler? FeatureRemoved; + public event EventHandler? FeatureRemoved; /// /// Invokes the Enabled event @@ -240,7 +241,7 @@ namespace Artemis.Core /// /// Invokes the FeatureAdded event /// - protected virtual void OnFeatureAdded(PluginFeatureEventArgs e) + protected virtual void OnFeatureAdded(PluginFeatureInfoEventArgs e) { FeatureAdded?.Invoke(this, e); } @@ -248,7 +249,7 @@ namespace Artemis.Core /// /// Invokes the FeatureRemoved event /// - protected virtual void OnFeatureRemoved(PluginFeatureEventArgs e) + protected virtual void OnFeatureRemoved(PluginFeatureInfoEventArgs e) { FeatureRemoved?.Invoke(this, e); } diff --git a/src/Artemis.Core/Plugins/PluginFeatureInfo.cs b/src/Artemis.Core/Plugins/PluginFeatureInfo.cs index 7fe5a20de..b8515197f 100644 --- a/src/Artemis.Core/Plugins/PluginFeatureInfo.cs +++ b/src/Artemis.Core/Plugins/PluginFeatureInfo.cs @@ -1,4 +1,5 @@ -using Artemis.Core.DataModelExpansions; +using System; +using Artemis.Core.DataModelExpansions; using Artemis.Core.DeviceProviders; using Artemis.Core.LayerBrushes; using Artemis.Core.LayerEffects; @@ -16,22 +17,48 @@ namespace Artemis.Core { private string? _description; private string? _icon; + private PluginFeature? _instance; private string _name = null!; - private PluginFeature _pluginFeature = null!; - internal PluginFeatureInfo() + internal PluginFeatureInfo(Plugin plugin, Type featureType, PluginFeatureAttribute? attribute) { + Plugin = plugin ?? throw new ArgumentNullException(nameof(plugin)); + FeatureType = featureType ?? throw new ArgumentNullException(nameof(featureType)); + + Name = attribute?.Name ?? featureType.Name.Humanize(LetterCasing.Title); + Description = attribute?.Description; + Icon = attribute?.Icon; + + if (Icon != null) return; + if (typeof(BaseDataModelExpansion).IsAssignableFrom(featureType)) + Icon = "TableAdd"; + else if (typeof(DeviceProvider).IsAssignableFrom(featureType)) + Icon = "Devices"; + else if (typeof(ProfileModule).IsAssignableFrom(featureType)) + Icon = "VectorRectangle"; + else if (typeof(Module).IsAssignableFrom(featureType)) + Icon = "GearBox"; + else if (typeof(LayerBrushProvider).IsAssignableFrom(featureType)) + Icon = "Brush"; + else if (typeof(LayerEffectProvider).IsAssignableFrom(featureType)) + Icon = "AutoAwesome"; + else + Icon = "Plugin"; } - internal PluginFeatureInfo(PluginFeature instance, PluginFeatureAttribute? attribute) + internal PluginFeatureInfo(Plugin plugin, PluginFeatureAttribute? attribute, PluginFeature instance) { + if (instance == null) throw new ArgumentNullException(nameof(instance)); + Plugin = plugin ?? throw new ArgumentNullException(nameof(plugin)); + FeatureType = instance.GetType(); + Name = attribute?.Name ?? instance.GetType().Name.Humanize(LetterCasing.Title); Description = attribute?.Description; Icon = attribute?.Icon; - PluginFeature = instance; + Instance = instance; if (Icon != null) return; - Icon = PluginFeature switch + Icon = Instance switch { BaseDataModelExpansion => "TableAdd", DeviceProvider => "Devices", @@ -43,6 +70,16 @@ namespace Artemis.Core }; } + /// + /// Gets the plugin this feature info is associated with + /// + public Plugin Plugin { get; } + + /// + /// Gets the type of the feature + /// + public Type FeatureType { get; } + /// /// The name of the plugin /// @@ -75,18 +112,18 @@ namespace Artemis.Core } /// - /// Gets the plugin this info is associated with + /// Gets the feature this info is associated with /// - public PluginFeature PluginFeature + public PluginFeature? Instance { - get => _pluginFeature; - internal set => SetAndNotify(ref _pluginFeature, value); + get => _instance; + internal set => SetAndNotify(ref _instance, value); } /// public override string ToString() { - return PluginFeature.Id; + return Instance?.Id ?? "Uninitialized feature"; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Interfaces/IRgbService.cs b/src/Artemis.Core/Services/Interfaces/IRgbService.cs index d478d8a00..565fb31a3 100644 --- a/src/Artemis.Core/Services/Interfaces/IRgbService.cs +++ b/src/Artemis.Core/Services/Interfaces/IRgbService.cs @@ -52,7 +52,7 @@ namespace Artemis.Core.Services bool IsRenderPaused { get; set; } /// - /// Recreates the Texture to use the given + /// Recreates the Texture to use the given /// /// void UpdateTexture(SKBitmap bitmap); @@ -119,7 +119,16 @@ namespace Artemis.Core.Services /// void SaveDevices(); + /// + /// Enables the provided device + /// + /// The device to enable void EnableDevice(ArtemisDevice device); + + /// + /// Disables the provided device + /// + /// The device to disable void DisableDevice(ArtemisDevice device); /// @@ -133,7 +142,7 @@ namespace Artemis.Core.Services event EventHandler DeviceRemoved; /// - /// Occurs when the surface has had modifications to its LED collection + /// Occurs when the surface has had modifications to its LED collection /// event EventHandler LedsChanged; } diff --git a/src/Artemis.Core/Services/ModuleService.cs b/src/Artemis.Core/Services/ModuleService.cs index 6d44ecb53..f71cfac76 100644 --- a/src/Artemis.Core/Services/ModuleService.cs +++ b/src/Artemis.Core/Services/ModuleService.cs @@ -67,7 +67,7 @@ namespace Artemis.Core.Services // If this is a profile module, animate profile disable // module.Deactivate would do the same but without animation if (module.IsActivated && module is ProfileModule profileModule) - await profileModule.ChangeActiveProfileAnimated(null, null); + await profileModule.ChangeActiveProfileAnimated(null, Enumerable.Empty()); module.Deactivate(false); } @@ -210,6 +210,7 @@ namespace Artemis.Core.Services List modules = _pluginManagementService.GetFeaturesOfType().ToList(); List tasks = new(); foreach (Module module in modules) + { lock (module) { bool shouldBeActivated = module.EvaluateActivationRequirements() && module.IsEnabled; @@ -218,6 +219,7 @@ namespace Artemis.Core.Services else if (!shouldBeActivated && module.IsActivated) tasks.Add(DeactivateModule(module)); } + } await Task.WhenAll(tasks); diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index 66b7933ec..952c62c4f 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -5,7 +5,6 @@ using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; -using System.Runtime.Loader; using Artemis.Core.DeviceProviders; using Artemis.Core.Ninject; using Artemis.Storage.Entities.Plugins; @@ -130,7 +129,11 @@ namespace Artemis.Core.Services { lock (_plugins) { - return _plugins.Where(p => p.IsEnabled).SelectMany(p => p.Features.Where(i => i.IsEnabled && i is T)).Cast().ToList(); + return _plugins.Where(p => p.IsEnabled) + .SelectMany(p => p.Features.Where(f => f.Instance != null && f.Instance.IsEnabled && f.Instance is T)) + .Select(f => f.Instance) + .Cast() + .ToList(); } } @@ -186,6 +189,7 @@ namespace Artemis.Core.Services // Load the plugin assemblies into the plugin context DirectoryInfo pluginDirectory = new(Path.Combine(Constants.DataFolder, "plugins")); foreach (DirectoryInfo subDirectory in pluginDirectory.EnumerateDirectories()) + { try { LoadPlugin(subDirectory); @@ -194,6 +198,7 @@ namespace Artemis.Core.Services { _logger.Warning(new ArtemisPluginException("Failed to load plugin", e), "Plugin exception"); } + } // ReSharper disable InconsistentlySynchronizedField - It's read-only, idc _logger.Debug("Loaded {count} plugin(s)", _plugins.Count); @@ -218,7 +223,7 @@ namespace Artemis.Core.Services foreach (Plugin plugin in _plugins.Where(p => p.Entity.IsEnabled)) EnablePlugin(plugin, false, ignorePluginLock); - _logger.Debug("Enabled {count} plugin(s)", _plugins.Where(p => p.IsEnabled).Sum(p => p.Features.Count(f => f.IsEnabled))); + _logger.Debug("Enabled {count} plugin(s)", _plugins.Count(p => p.IsEnabled)); // ReSharper restore InconsistentlySynchronizedField LoadingPlugins = false; @@ -289,6 +294,28 @@ namespace Artemis.Core.Services throw new ArtemisPluginException(plugin, "Failed to load the plugins assembly", e); } + // Get the Plugin feature from the main assembly and if there is only one, instantiate it + List featureTypes; + try + { + featureTypes = plugin.Assembly.GetTypes().Where(t => typeof(PluginFeature).IsAssignableFrom(t)).ToList(); + } + catch (ReflectionTypeLoadException e) + { + throw new ArtemisPluginException( + plugin, + "Failed to initialize the plugin assembly", + // ReSharper disable once RedundantEnumerableCastCall - Casting from nullable to non-nullable here + new AggregateException(e.LoaderExceptions.Where(le => le != null).Cast().ToArray()) + ); + } + + foreach (Type featureType in featureTypes) + plugin.AddFeature(new PluginFeatureInfo(plugin, featureType, (PluginFeatureAttribute?) Attribute.GetCustomAttribute(featureType, typeof(PluginFeatureAttribute)))); + + if (!featureTypes.Any()) + _logger.Warning("Plugin {plugin} contains no features", plugin); + List bootstrappers = plugin.Assembly.GetTypes().Where(t => typeof(IPluginBootstrapper).IsAssignableFrom(t)).ToList(); if (bootstrappers.Count > 1) _logger.Warning($"{plugin} has more than one bootstrapper, only initializing {bootstrappers.First().FullName}"); @@ -315,60 +342,45 @@ namespace Artemis.Core.Services plugin.SetEnabled(true); - // Get the Plugin feature from the main assembly and if there is only one, instantiate it - List featureTypes; - try - { - featureTypes = plugin.Assembly.GetTypes().Where(t => typeof(PluginFeature).IsAssignableFrom(t)).ToList(); - } - catch (ReflectionTypeLoadException e) - { - throw new ArtemisPluginException( - plugin, - "Failed to initialize the plugin assembly", - // ReSharper disable once RedundantEnumerableCastCall - Casting from nullable to non-nullable here - new AggregateException(e.LoaderExceptions.Where(le => le != null).Cast().ToArray()) - ); - } - - if (!featureTypes.Any()) - _logger.Warning("Plugin {plugin} contains no features", plugin); - - // Create instances of each feature and add them to the plugin + // Create instances of each feature // Construction should be simple and not contain any logic so failure at this point means the entire plugin fails - foreach (Type featureType in featureTypes) + foreach (PluginFeatureInfo featureInfo in plugin.Features) + { try { - plugin.Kernel.Bind(featureType).ToSelf().InSingletonScope(); + plugin.Kernel.Bind(featureInfo.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); + PluginFeature instance = (PluginFeature) plugin.Kernel.Get(featureInfo.FeatureType, parameters); // Get the PluginFeature attribute which contains extra info on the feature - PluginFeatureAttribute? pluginFeatureAttribute = (PluginFeatureAttribute?) Attribute.GetCustomAttribute(featureType, typeof(PluginFeatureAttribute)); - instance.Info = new PluginFeatureInfo(instance, pluginFeatureAttribute); - plugin.AddFeature(instance); + featureInfo.Instance = instance; + instance.Info = featureInfo; + instance.Plugin = plugin; // Load the enabled state and if not found, default to true - instance.Entity = plugin.Entity.Features.FirstOrDefault(i => i.Type == featureType.FullName) ?? - new PluginFeatureEntity {IsEnabled = plugin.Info.AutoEnableFeatures, Type = featureType.FullName!}; + instance.Entity = plugin.Entity.Features.FirstOrDefault(i => i.Type == featureInfo.FeatureType.FullName) ?? + new PluginFeatureEntity {IsEnabled = plugin.Info.AutoEnableFeatures, Type = featureInfo.FeatureType.FullName!}; } catch (Exception e) { _logger.Warning(new ArtemisPluginException(plugin, "Failed to instantiate feature", e), "Failed to instantiate feature", plugin); } + } - // Activate plugins after they are all loaded - foreach (PluginFeature pluginFeature in plugin.Features.Where(i => i.Entity.IsEnabled)) + // Activate features after they are all loaded + foreach (PluginFeatureInfo pluginFeature in plugin.Features.Where(f => f.Instance != null && f.Instance.Entity.IsEnabled)) + { try { - EnablePluginFeature(pluginFeature, false, !ignorePluginLock); + EnablePluginFeature(pluginFeature.Instance!, false, !ignorePluginLock); } catch (Exception) { // ignored, logged in EnablePluginFeature } + } if (saveState) { @@ -406,12 +418,10 @@ namespace Artemis.Core.Services if (!plugin.IsEnabled) return; - while (plugin.Features.Any()) + foreach (PluginFeatureInfo pluginFeatureInfo in plugin.Features) { - PluginFeature feature = plugin.Features[0]; - if (feature.IsEnabled) - DisablePluginFeature(feature, false); - plugin.RemoveFeature(feature); + if (pluginFeatureInfo.Instance != null && pluginFeatureInfo.Instance.IsEnabled) + DisablePluginFeature(pluginFeatureInfo.Instance, false); } plugin.SetEnabled(false); @@ -450,7 +460,6 @@ namespace Artemis.Core.Services Plugin? existing = _plugins.FirstOrDefault(p => p.Guid == pluginInfo.Guid); if (existing != null) - { try { RemovePlugin(existing); @@ -459,7 +468,6 @@ namespace Artemis.Core.Services { throw new ArtemisPluginException("A plugin with the same GUID is already loaded, failed to remove old version", e); } - } string targetDirectory = pluginInfo.Main.Split(".dll")[0].Replace("/", "").Replace("\\", ""); string uniqueTargetDirectory = targetDirectory; @@ -582,9 +590,11 @@ namespace Artemis.Core.Services private void SavePlugin(Plugin plugin) { - foreach (PluginFeature pluginFeature in plugin.Features) - if (plugin.Entity.Features.All(i => i.Type != pluginFeature.GetType().FullName)) - plugin.Entity.Features.Add(pluginFeature.Entity); + foreach (PluginFeatureInfo featureInfo in plugin.Features.Where(i => i.Instance != null)) + { + if (plugin.Entity.Features.All(i => i.Type != featureInfo.FeatureType.FullName)) + plugin.Entity.Features.Add(featureInfo.Instance!.Entity); + } _pluginRepository.SavePlugin(plugin.Entity); } diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index e69ca7f4f..bdae901c8 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -146,7 +146,8 @@ namespace Artemis.Core.Services void ActivatingProfilePluginToggle(object? sender, PluginEventArgs e) { - InstantiateProfile(profile); + if (!profile.Disposed) + InstantiateProfile(profile); } // This could happen during activation so subscribe to it diff --git a/src/Artemis.Core/Utilities/CorePluginFeature.cs b/src/Artemis.Core/Utilities/CorePluginFeature.cs index 897c46b88..66dcb40ff 100644 --- a/src/Artemis.Core/Utilities/CorePluginFeature.cs +++ b/src/Artemis.Core/Utilities/CorePluginFeature.cs @@ -9,7 +9,7 @@ namespace Artemis.Core { public CorePluginFeature() { - Constants.CorePlugin.AddFeature(this); + Constants.CorePlugin.AddFeature(new PluginFeatureInfo(Constants.CorePlugin, null, this)); IsEnabled = true; } diff --git a/src/Artemis.Core/Utilities/IntroAnimation.cs b/src/Artemis.Core/Utilities/IntroAnimation.cs index f2f1bb4fb..4b6d36ffd 100644 --- a/src/Artemis.Core/Utilities/IntroAnimation.cs +++ b/src/Artemis.Core/Utilities/IntroAnimation.cs @@ -44,7 +44,7 @@ namespace Artemis.Core foreach (LayerEntity profileEntityLayer in profileEntity.Layers) profileEntityLayer.Leds.AddRange(_devices.SelectMany(d => d.Leds).Select(l => new LedEntity { - DeviceIdentifier = l.Device.RgbDevice.GetDeviceIdentifier(), + DeviceIdentifier = l.Device.Identifier, LedName = l.RgbLed.Id.ToString() })); diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index 8dd40960e..2d0190136 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -39,7 +39,7 @@ namespace Artemis.UI.Ninject.Factories public interface ISettingsVmFactory : IVmFactory { PluginSettingsViewModel CreatePluginSettingsViewModel(Plugin plugin); - PluginFeatureViewModel CreatePluginFeatureViewModel(PluginFeature feature); + PluginFeatureViewModel CreatePluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo); DeviceSettingsViewModel CreateDeviceSettingsViewModel(ArtemisDevice device); } diff --git a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index b6bbf11aa..59856ce6f 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -136,7 +136,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions RenderProfileElement.DisplayCondition.ChildRemoved += DisplayConditionOnChildrenModified; } - private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e) + private void CoreServiceOnFrameRendered(object sender, FrameRenderedEventArgs e) { ActiveItem?.Evaluate(); } diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs index 00c72fc34..aee7265a0 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingsViewModel.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Artemis.Core; using Artemis.UI.Ninject.Factories; @@ -14,7 +13,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings { private readonly IDataBindingsVmFactory _dataBindingsVmFactory; private readonly IProfileEditorService _profileEditorService; - private ILayerProperty? _selectedDataBinding; + private ILayerProperty _selectedDataBinding; private int _selectedItemIndex; private bool _updating; @@ -95,7 +94,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings SubscribeToSelectedDataBinding(); base.OnInitialActivate(); } - + protected override void OnActivate() { SelectedItemIndex = 0; diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml index bd8bc03ff..ad2358da7 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml @@ -23,7 +23,7 @@ - + - + Feature enabled diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs index dd56dabc4..f7a0899d1 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs @@ -16,7 +16,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins private bool _enabling; private readonly IMessageService _messageService; - public PluginFeatureViewModel(PluginFeature feature, + public PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, IDialogService dialogService, IPluginManagementService pluginManagementService, IMessageService messageService) @@ -25,11 +25,11 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins _pluginManagementService = pluginManagementService; _messageService = messageService; - Feature = feature; + FeatureInfo = pluginFeatureInfo; } - public PluginFeature Feature { get; } - public Exception LoadException => Feature.LoadException; + public PluginFeatureInfo FeatureInfo { get; } + public Exception LoadException => FeatureInfo.Instance?.LoadException; public bool Enabling { @@ -39,7 +39,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins public bool IsEnabled { - get => Feature.IsEnabled; + get => FeatureInfo.Instance != null && FeatureInfo.Instance.IsEnabled; set => Task.Run(() => UpdateEnabled(value)); } @@ -60,7 +60,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins if (LoadException == null) return; - _dialogService.ShowExceptionDialog("Feature failed to enable", Feature.LoadException); + _dialogService.ShowExceptionDialog("Feature failed to enable", LoadException); } protected override void OnInitialActivate() @@ -83,7 +83,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins private async Task UpdateEnabled(bool enable) { - if (IsEnabled == enable) + if (IsEnabled == enable || FeatureInfo.Instance == null) { NotifyOfPropertyChange(nameof(IsEnabled)); return; @@ -95,11 +95,11 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins try { - await Task.Run(() => _pluginManagementService.EnablePluginFeature(Feature, true)); + await Task.Run(() => _pluginManagementService.EnablePluginFeature(FeatureInfo.Instance, true)); } catch (Exception e) { - _messageService.ShowMessage($"Failed to enable {Feature.Info.Name}\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder); + _messageService.ShowMessage($"Failed to enable {FeatureInfo.Name}\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder); } finally { @@ -108,7 +108,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins } else { - _pluginManagementService.DisablePluginFeature(Feature, true); + _pluginManagementService.DisablePluginFeature(FeatureInfo.Instance, true); NotifyOfPropertyChange(nameof(IsEnabled)); } } @@ -117,13 +117,13 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins private void OnFeatureEnabling(object sender, PluginFeatureEventArgs e) { - if (e.PluginFeature != Feature) return; + if (e.PluginFeature != FeatureInfo.Instance) return; Enabling = true; } private void OnFeatureEnableStopped(object sender, PluginFeatureEventArgs e) { - if (e.PluginFeature != Feature) return; + if (e.PluginFeature != FeatureInfo.Instance) return; Enabling = false; NotifyOfPropertyChange(nameof(IsEnabled)); diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml index 746ef06fc..92bb2ad41 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml @@ -116,21 +116,11 @@ Plugin features - - Enable the plugin to view its features - - + VirtualizingPanel.ScrollUnit="Pixel"> diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs index f8394b325..77cdc5d6d 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs @@ -117,22 +117,12 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins protected override void OnInitialActivate() { - Plugin.FeatureAdded += PluginOnFeatureAdded; - Plugin.FeatureRemoved += PluginOnFeatureRemoved; - foreach (PluginFeature pluginFeature in Plugin.Features) - Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeature)); + foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features) + Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo)); base.OnInitialActivate(); } - protected override void OnClose() - { - Plugin.FeatureAdded -= PluginOnFeatureAdded; - Plugin.FeatureRemoved -= PluginOnFeatureRemoved; - - base.OnClose(); - } - private async Task UpdateEnabled(bool enable) { if (IsEnabled == enable) @@ -178,17 +168,5 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins NotifyOfPropertyChange(nameof(IsEnabled)); NotifyOfPropertyChange(nameof(CanOpenSettings)); } - - private void PluginOnFeatureRemoved(object sender, PluginFeatureEventArgs e) - { - PluginFeatureViewModel viewModel = Items.FirstOrDefault(i => i.Feature == e.PluginFeature); - if (viewModel != null) - Items.Remove(viewModel); - } - - private void PluginOnFeatureAdded(object sender, PluginFeatureEventArgs e) - { - Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(e.PluginFeature)); - } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepViewModel.cs b/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepViewModel.cs index 03f9c17d7..16e677d38 100644 --- a/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepViewModel.cs +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepViewModel.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Artemis.Core; using Artemis.Core.DeviceProviders; using Artemis.Core.Services; using Artemis.UI.Ninject.Factories; @@ -27,9 +28,8 @@ namespace Artemis.UI.Screens.StartupWizard.Steps Items.Clear(); // _pluginManagementService.GetFeaturesOfType<>() will only give us enabled features so lets get all of them this way - IEnumerable features = _pluginManagementService.GetAllPlugins() - .SelectMany(p => p.Features.Where(f => f is DeviceProvider)) - .Cast() + IEnumerable features = _pluginManagementService.GetAllPlugins() + .SelectMany(p => p.Features.Where(f => typeof(DeviceProvider).IsAssignableFrom(f.FeatureType))) .OrderBy(d => d.GetType().Name); Items.AddRange(features.Select(d => _settingsVmFactory.CreatePluginFeatureViewModel(d))); diff --git a/src/Artemis.UI/Services/DeviceLayoutService.cs b/src/Artemis.UI/Services/DeviceLayoutService.cs index dd7a6a83d..d224ae36b 100644 --- a/src/Artemis.UI/Services/DeviceLayoutService.cs +++ b/src/Artemis.UI/Services/DeviceLayoutService.cs @@ -59,7 +59,7 @@ namespace Artemis.UI.Services #region Event handlers - private async void WindowServiceOnMainWindowOpened(object? sender, EventArgs e) + private async void WindowServiceOnMainWindowOpened(object sender, EventArgs e) { List devices = _rgbService.Devices.Where(device => DeviceNeedsLayout(device) && !_ignoredDevices.Contains(device)).ToList(); foreach (ArtemisDevice artemisDevice in devices) From 98b97702505c7445824f66965293bd84d920b259 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 11 Mar 2021 00:44:46 +0100 Subject: [PATCH 2/5] Device properties - UI polish (the shiny kind) Conditions - Open existing condition path on click --- src/Artemis.Core/Services/RgbService.cs | 3 +- .../Controls/DeviceVisualizerLed.cs | 24 +- .../Input/DataModelDynamicViewModel.cs | 22 +- .../Settings/Device/DeviceDialogView.xaml | 220 ++++++++++-------- .../Settings/Device/DeviceDialogViewModel.cs | 37 ++- .../Device/Tabs/DeviceInfoTabView.xaml | 21 +- .../Device/Tabs/DeviceInfoTabViewModel.cs | 23 +- .../Device/Tabs/DevicePropertiesTabView.xaml | 33 ++- .../Tabs/DevicePropertiesTabViewModel.cs | 15 +- 9 files changed, 263 insertions(+), 135 deletions(-) diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index 30e359658..6d322b8d0 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -197,7 +197,7 @@ namespace Artemis.Core.Services public ArtemisLayout ApplyBestDeviceLayout(ArtemisDevice device) { ArtemisLayout layout; - + // Configured layout path takes precedence over all other options if (device.CustomLayoutPath != null) { @@ -239,6 +239,7 @@ namespace Artemis.Core.Services public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout, bool createMissingLeds, bool removeExessiveLeds) { device.ApplyLayout(layout, createMissingLeds, removeExessiveLeds); + UpdateLedGroup(); } public ArtemisDevice? GetDevice(IRGBDevice rgbDevice) diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs index 35adaf092..640812016 100644 --- a/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs +++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs @@ -17,7 +17,7 @@ namespace Artemis.UI.Shared private SolidColorBrush? _renderColorBrush; private Color _renderColor; - + public DeviceVisualizerLed(ArtemisLed led) { Led = led; @@ -51,7 +51,7 @@ namespace Artemis.UI.Shared byte g = Led.RgbLed.Color.GetG(); byte b = Led.RgbLed.Color.GetB(); - _renderColor.A = (byte)(isDimmed ? 100 : 255); + _renderColor.A = (byte) (isDimmed ? 100 : 255); _renderColor.A = isDimmed ? Dimmed : NonDimmed; _renderColor.R = r; _renderColor.G = g; @@ -135,6 +135,8 @@ namespace Artemis.UI.Shared { try { + double width = Led.RgbLed.Size.Width - deflateAmount; + double height = Led.RgbLed.Size.Height - deflateAmount; // DisplayGeometry = Geometry.Parse(Led.RgbLed.ShapeData); DisplayGeometry = Geometry.Combine( Geometry.Empty, @@ -144,11 +146,27 @@ namespace Artemis.UI.Shared { Children = new TransformCollection { - new ScaleTransform(Led.RgbLed.Size.Width - deflateAmount, Led.RgbLed.Size.Height - deflateAmount), + new ScaleTransform(width, height), new TranslateTransform(deflateAmount / 2, deflateAmount / 2) } } ); + + if (DisplayGeometry.Bounds.Width > width) + { + DisplayGeometry = Geometry.Combine(Geometry.Empty, DisplayGeometry, GeometryCombineMode.Union, new TransformGroup + { + Children = new TransformCollection {new ScaleTransform(width / DisplayGeometry.Bounds.Width, 1)} + }); + } + + if (DisplayGeometry.Bounds.Height > height) + { + DisplayGeometry = Geometry.Combine(Geometry.Empty, DisplayGeometry, GeometryCombineMode.Union, new TransformGroup + { + Children = new TransformCollection {new ScaleTransform(1, height / DisplayGeometry.Bounds.Height)} + }); + } } catch (Exception) { diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs index 55ac8e376..f7ead0cbb 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Timers; @@ -139,7 +140,11 @@ namespace Artemis.UI.Shared.Input set { if (!SetAndNotify(ref _isDataModelViewModelOpen, value)) return; - if (value) UpdateDataModelVisualization(); + if (value) + { + UpdateDataModelVisualization(); + OpenSelectedValue(DataModelViewModel); + } } } @@ -303,6 +308,21 @@ namespace Artemis.UI.Shared.Input extraDataModelViewModel.Update(_dataModelUIService, new DataModelUpdateConfiguration(LoadEventChildren)); } + private void OpenSelectedValue(DataModelVisualizationViewModel dataModelPropertiesViewModel) + { + if (DataModelPath == null) + return; + + if (dataModelPropertiesViewModel.Children.Any(c => c.DataModelPath != null && DataModelPath.Path.StartsWith(c.DataModelPath.Path))) + { + dataModelPropertiesViewModel.IsVisualizationExpanded = true; + foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in dataModelPropertiesViewModel.Children) + { + OpenSelectedValue(dataModelVisualizationViewModel); + } + } + } + #endregion #region Events diff --git a/src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml index 7b82c904e..9e8dc0c23 100644 --- a/src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml +++ b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogView.xaml @@ -24,110 +24,124 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs index 97d3ce8fb..c1472c721 100644 --- a/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs @@ -9,6 +9,7 @@ using Artemis.Core.Services; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.Shared; using Artemis.UI.Shared.Services; +using MaterialDesignThemes.Wpf; using Ookii.Dialogs.Wpf; using RGB.NET.Layout; using Stylet; @@ -21,6 +22,7 @@ namespace Artemis.UI.Screens.Settings.Device private readonly IDialogService _dialogService; private readonly IRgbService _rgbService; private ArtemisLed _selectedLed; + private SnackbarMessageQueue _deviceMessageQueue; public DeviceDialogViewModel(ArtemisDevice device, IDeviceService deviceService, IRgbService rgbService, IDialogService dialogService, IDeviceDebugVmFactory factory) { @@ -38,9 +40,28 @@ namespace Artemis.UI.Screens.Settings.Device DisplayName = $"{device.RgbDevice.DeviceInfo.Model} | Artemis"; } + protected override void OnInitialActivate() + { + DeviceMessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(5)); + Device.DeviceUpdated += DeviceOnDeviceUpdated; + base.OnInitialActivate(); + } + + protected override void OnClose() + { + Device.DeviceUpdated -= DeviceOnDeviceUpdated; + base.OnClose(); + } + public ArtemisDevice Device { get; } public PanZoomViewModel PanZoomViewModel { get; } - + + public SnackbarMessageQueue DeviceMessageQueue + { + get => _deviceMessageQueue; + set => SetAndNotify(ref _deviceMessageQueue, value); + } + public ArtemisLed SelectedLed { get => _selectedLed; @@ -50,7 +71,10 @@ namespace Artemis.UI.Screens.Settings.Device NotifyOfPropertyChange(nameof(SelectedLeds)); } } - public List SelectedLeds => SelectedLed != null ? new List { SelectedLed } : null; + + public bool CanExportLayout => Device.Layout?.IsValid ?? false; + + public List SelectedLeds => SelectedLed != null ? new List {SelectedLed} : null; public bool CanOpenImageDirectory => Device.Layout?.Image != null; @@ -165,5 +189,14 @@ namespace Artemis.UI.Screens.Settings.Device #endregion // ReSharper restore UnusedMember.Global + + #region Event handlers + + private void DeviceOnDeviceUpdated(object? sender, EventArgs e) + { + NotifyOfPropertyChange(nameof(CanExportLayout)); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabView.xaml b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabView.xaml index 5b4ce48a4..3f5047fb9 100644 --- a/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabView.xaml +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabView.xaml @@ -91,15 +91,21 @@ - Layout file path - + Text="{Binding DefaultLayoutPath}" /> @@ -108,14 +114,19 @@ Image file path - + Text="{Binding Device.Layout.Image.LocalPath}" /> \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabViewModel.cs index e386b676a..bf97b4e21 100644 --- a/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DeviceInfoTabViewModel.cs @@ -1,4 +1,5 @@ -using Artemis.Core; +using System.Windows; +using Artemis.Core; using RGB.NET.Core; using Stylet; @@ -6,6 +7,8 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs { public class DeviceInfoTabViewModel : Screen { + private string _defaultLayoutPath; + public DeviceInfoTabViewModel(ArtemisDevice device) { Device = device; @@ -14,5 +17,23 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs public bool IsKeyboard => Device.RgbDevice.DeviceInfo.DeviceType == RGBDeviceType.Keyboard; public ArtemisDevice Device { get; } + + public string DefaultLayoutPath + { + get => _defaultLayoutPath; + set => SetAndNotify(ref _defaultLayoutPath, value); + } + + public void CopyToClipboard(string content) + { + Clipboard.SetText(content); + ((DeviceDialogViewModel) Parent).DeviceMessageQueue.Enqueue("Copied path to clipboard."); + } + + protected override void OnInitialActivate() + { + DefaultLayoutPath = Device.DeviceProvider.LoadLayout(Device).FilePath; + base.OnInitialActivate(); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabView.xaml b/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabView.xaml index b6e4afd4e..0d69ed4ee 100644 --- a/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabView.xaml +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabView.xaml @@ -174,20 +174,31 @@ - - - - - - + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs index 19f425e70..59f3ddc57 100644 --- a/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs @@ -15,7 +15,6 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs public class DevicePropertiesTabViewModel : Screen { private readonly ICoreService _coreService; - private readonly IMessageService _messageService; private readonly IDialogService _dialogService; private readonly IRgbService _rgbService; private float _blueScale; @@ -34,13 +33,11 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs public DevicePropertiesTabViewModel(ArtemisDevice device, ICoreService coreService, IRgbService rgbService, - IMessageService messageService, IDialogService dialogService, IModelValidator validator) : base(validator) { _coreService = coreService; _rgbService = rgbService; - _messageService = messageService; _dialogService = dialogService; Device = device; @@ -115,7 +112,7 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs if (e.OriginalSource is Button) { Device.CustomLayoutPath = null; - _messageService.ShowMessage("Cleared imported layout"); + ((DeviceDialogViewModel) Parent).DeviceMessageQueue.Enqueue("Cleared imported layout."); return; } @@ -126,13 +123,13 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs if (result == true) { Device.CustomLayoutPath = dialog.FileName; - _messageService.ShowMessage($"Imported layout from {dialog.FileName}"); + ((DeviceDialogViewModel) Parent).DeviceMessageQueue.Enqueue($"Imported layout from {dialog.FileName}."); } } public async Task SelectPhysicalLayout() { - await _dialogService.ShowDialog(new Dictionary {{"device", Device}}); + await _dialogService.ShowDialogAt("DeviceDialog", new Dictionary {{"device", Device}}); } public async Task Apply() @@ -151,7 +148,8 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs Device.RedScale = RedScale / 100f; Device.GreenScale = GreenScale / 100f; Device.BlueScale = BlueScale / 100f; - + _rgbService.SaveDevice(Device); + _coreService.ModuleRenderingDisabled = false; } @@ -195,7 +193,8 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs private void DeviceOnPropertyChanged(object sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Device.CustomLayoutPath)) _rgbService.ApplyBestDeviceLayout(Device); + if (e.PropertyName == nameof(Device.CustomLayoutPath)) + _rgbService.ApplyBestDeviceLayout(Device); } private void OnFrameRendering(object sender, FrameRenderingEventArgs e) From f829743b6c0be3a6899435a468da8643c85aa49b Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 11 Mar 2021 19:32:25 +0100 Subject: [PATCH 3/5] Updating - Fix crash when client has internet Updating - Check for updates periodically and when window opens (if auto-update enabled) Webserver - Don't write wildcard to webserver.txt --- src/Artemis.Core/Constants.cs | 11 ++- src/Artemis.Core/Services/Core/BuildInfo.cs | 5 ++ .../Services/WebServer/WebServerService.cs | 2 +- src/Artemis.UI/Artemis.UI.csproj | 3 - .../Settings/Dialogs/UpdateDialogView.xaml | 21 ++++-- .../Settings/Dialogs/UpdateDialogViewModel.cs | 69 ++++++++++++------- .../General/GeneralSettingsTabViewModel.cs | 18 +++-- src/Artemis.UI/Services/UpdateService.cs | 42 ++++++++--- src/Artemis.UI/buildinfo.json | 6 -- 9 files changed, 120 insertions(+), 57 deletions(-) delete mode 100644 src/Artemis.UI/buildinfo.json diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs index effc348c2..96e72a0f6 100644 --- a/src/Artemis.Core/Constants.cs +++ b/src/Artemis.Core/Constants.cs @@ -46,7 +46,14 @@ namespace Artemis.Core /// public static readonly BuildInfo BuildInfo = File.Exists(Path.Combine(ApplicationFolder, "buildinfo.json")) ? JsonConvert.DeserializeObject(File.ReadAllText(Path.Combine(ApplicationFolder, "buildinfo.json"))) - : new BuildInfo(); + : new BuildInfo + { + IsLocalBuild = true, + BuildId = 1337, + BuildNumber = 1337, + SourceBranch = "local", + SourceVersion = "local" + }; /// /// The plugin used by core components of Artemis @@ -108,6 +115,6 @@ namespace Artemis.Core typeof(float), typeof(double), typeof(decimal) - }; + }; } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Core/BuildInfo.cs b/src/Artemis.Core/Services/Core/BuildInfo.cs index a2caaa3cf..5de246d43 100644 --- a/src/Artemis.Core/Services/Core/BuildInfo.cs +++ b/src/Artemis.Core/Services/Core/BuildInfo.cs @@ -38,5 +38,10 @@ namespace Artemis.Core.Services.Core /// [JsonProperty] public string SourceVersion { get; internal set; } = null!; + + /// + /// Gets a boolean indicating whether the current build is a local build + /// + public bool IsLocalBuild { get; internal set; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/WebServer/WebServerService.cs b/src/Artemis.Core/Services/WebServer/WebServerService.cs index d74035da2..55c7cbcfb 100644 --- a/src/Artemis.Core/Services/WebServer/WebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/WebServerService.cs @@ -61,7 +61,7 @@ namespace Artemis.Core.Services server.StateChanged += (s, e) => _logger.Verbose("WebServer new state - {state}", e.NewState); // Store the URL in a webserver.txt file so that remote applications can find it - File.WriteAllText(Path.Combine(Constants.DataFolder, "webserver.txt"), url); + File.WriteAllText(Path.Combine(Constants.DataFolder, "webserver.txt"), $"http://localhost:{_webServerPortSetting.Value}/"); return server; } diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index 451ff0b03..127c6a5c9 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -334,9 +334,6 @@ - - PreserveNewest - SettingsSingleFileGenerator Settings.Designer.cs diff --git a/src/Artemis.UI/Screens/Settings/Dialogs/UpdateDialogView.xaml b/src/Artemis.UI/Screens/Settings/Dialogs/UpdateDialogView.xaml index 88ab9c7de..583f3828f 100644 --- a/src/Artemis.UI/Screens/Settings/Dialogs/UpdateDialogView.xaml +++ b/src/Artemis.UI/Screens/Settings/Dialogs/UpdateDialogView.xaml @@ -32,13 +32,20 @@ - - - - - - - + + + + + + + + + + Couldn't retrieve changes, sorry :( + +