diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index 8f875aaf6..a1f8400e8 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -99,11 +99,12 @@ namespace Artemis.Core /// public override void Reset() { - DisplayConditionMet = false; - Timeline.JumpToEnd(); + UpdateDisplayCondition(); - foreach (ProfileElement child in Children) - child.Reset(); + if (DisplayConditionMet) + Timeline.JumpToStart(); + else + Timeline.JumpToEnd(); } /// @@ -202,13 +203,13 @@ namespace Artemis.Core foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended)) baseLayerEffect.PreProcess(canvas, rendererBounds, layerPaint); - canvas.SaveLayer(layerPaint); - canvas.Translate(Bounds.Left - basePosition.X, Bounds.Top - basePosition.Y); - // No point rendering if the alpha was set to zero by one of the effects if (layerPaint.Color.Alpha == 0) return; + canvas.SaveLayer(layerPaint); + canvas.Translate(Bounds.Left - basePosition.X, Bounds.Top - basePosition.Y); + // Iterate the children in reverse because the first layer must be rendered last to end up on top for (int index = Children.Count - 1; index > -1; index--) Children[index].Render(canvas, new SKPointI(Bounds.Left, Bounds.Top)); @@ -243,6 +244,7 @@ namespace Artemis.Core internal override void Load() { ExpandedPropertyGroups.AddRange(FolderEntity.ExpandedPropertyGroups); + Reset(); // Load child folders foreach (FolderEntity childFolder in Profile.ProfileEntity.Folders.Where(f => f.ParentId == EntityId)) diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 6c1b74da8..f4f403100 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -172,6 +172,9 @@ namespace Artemis.Core Disposed = true; + LayerBrushStore.LayerBrushAdded -= LayerBrushStoreOnLayerBrushAdded; + LayerBrushStore.LayerBrushRemoved -= LayerBrushStoreOnLayerBrushRemoved; + // Brush first in case it depends on any of the other disposables during it's own disposal _layerBrush?.Dispose(); _general.Dispose(); @@ -326,8 +329,12 @@ namespace Artemis.Core /// public override void Reset() { - DisplayConditionMet = false; - Timeline.JumpToEnd(); + UpdateDisplayCondition(); + + if (DisplayConditionMet) + Timeline.JumpToStart(); + else + Timeline.JumpToEnd(); } /// @@ -355,8 +362,6 @@ namespace Artemis.Core if (Enabled) return; - Debug.WriteLine($"Enabling {this}"); - LayerBrush?.InternalEnable(); foreach (BaseLayerEffect baseLayerEffect in LayerEffects) baseLayerEffect.InternalEnable(); @@ -370,8 +375,6 @@ namespace Artemis.Core if (!Enabled) return; - Debug.WriteLine($"Disabling {this}"); - LayerBrush?.InternalDisable(); foreach (BaseLayerEffect baseLayerEffect in LayerEffects) baseLayerEffect.InternalDisable(); diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index f5df222be..559feecdc 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -125,6 +125,7 @@ namespace Artemis.Core bool stickToMainSegment = Timeline.PlayMode == TimelinePlayMode.Repeat && DisplayConditionMet; if (DisplayCondition != null && DisplayCondition.ContainsEvents) stickToMainSegment = false; + Timeline.Update(TimeSpan.FromSeconds(deltaTime), stickToMainSegment); } diff --git a/src/Artemis.Core/Models/Profile/Timeline.cs b/src/Artemis.Core/Models/Profile/Timeline.cs index 9c1fc13a7..9fb9a201f 100644 --- a/src/Artemis.Core/Models/Profile/Timeline.cs +++ b/src/Artemis.Core/Models/Profile/Timeline.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Threading; using Artemis.Storage.Entities.Profile; namespace Artemis.Core @@ -322,7 +323,7 @@ namespace Artemis.Core IsOverridden = false; _lastOverridePosition = Position; - + if (stickToMainSegment && Position > MainSegmentEndPosition) { // If the main segment has no length, simply stick to the start of the segment diff --git a/src/Artemis.Core/Plugins/Modules/Module.cs b/src/Artemis.Core/Plugins/Modules/Module.cs index 3e0af867d..edf3cb340 100644 --- a/src/Artemis.Core/Plugins/Modules/Module.cs +++ b/src/Artemis.Core/Plugins/Modules/Module.cs @@ -61,7 +61,6 @@ namespace Artemis.Core.Modules } } - /// /// For internal use only, please use . /// @@ -79,20 +78,15 @@ namespace Artemis.Core.Modules /// Gets a read only collection of default profile paths /// public IReadOnlyCollection<(DefaultCategoryName, string)> DefaultProfilePaths => _defaultProfilePaths.AsReadOnly(); - + /// - /// The modules display name that's shown in the menu - /// - public string? DisplayName { get; protected set; } - - /// - /// The modules display icon that's shown in the UI accepts: + /// A list of activation requirements /// - /// Either set to the name of a Material Icon see ( for available - /// icons) or set to a path relative to the plugin folder pointing to a .svg file + /// If this list is not and not empty becomes + /// and the data of this module is only available to profiles specifically targeting it. /// /// - public string? DisplayIcon { get; set; } + public abstract List? ActivationRequirements { get; } /// /// Gets whether this module is activated. A module can only be active while its @@ -114,12 +108,6 @@ namespace Artemis.Core.Modules /// public bool UpdateDuringActivationOverride { get; protected set; } - /// - /// A list of activation requirements - /// Note: if empty the module is always activated - /// - public List ActivationRequirements { get; } = new(); - /// /// Gets or sets the activation requirement mode, defaults to /// @@ -133,7 +121,7 @@ namespace Artemis.Core.Modules /// /// /// - public bool IsAlwaysAvailable => ActivationRequirements.Count == 0; + public bool IsAlwaysAvailable => ActivationRequirements == null || ActivationRequirements.Count == 0; /// /// Gets whether updating this module is currently allowed @@ -181,12 +169,12 @@ namespace Artemis.Core.Modules /// The evaluated result of the activation requirements public bool EvaluateActivationRequirements() { - if (!ActivationRequirements.Any()) + if (IsAlwaysAvailable) return true; if (ActivationRequirementMode == ActivationRequirementType.All) - return ActivationRequirements.All(r => r.Evaluate()); + return ActivationRequirements!.All(r => r.Evaluate()); if (ActivationRequirementMode == ActivationRequirementType.Any) - return ActivationRequirements.Any(r => r.Evaluate()); + return ActivationRequirements!.Any(r => r.Evaluate()); return false; } diff --git a/src/Artemis.Core/Plugins/PluginFeatureInfo.cs b/src/Artemis.Core/Plugins/PluginFeatureInfo.cs index 390e7419e..a7b468835 100644 --- a/src/Artemis.Core/Plugins/PluginFeatureInfo.cs +++ b/src/Artemis.Core/Plugins/PluginFeatureInfo.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Artemis.Core.DataModelExpansions; using Artemis.Core.DeviceProviders; using Artemis.Core.LayerBrushes; using Artemis.Core.LayerEffects; @@ -132,6 +131,19 @@ namespace Artemis.Core internal set => SetAndNotify(ref _instance, value); } + /// + /// Gets a string representing either a full path pointing to an svg or the markdown icon + /// + public string? ResolvedIcon + { + get + { + if (Icon == null) + return null; + return Icon.EndsWith(".svg") ? Plugin.ResolveRelativePath(Icon) : Icon; + } + } + internal PluginFeatureEntity Entity { get; } /// diff --git a/src/Artemis.Core/Plugins/PluginInfo.cs b/src/Artemis.Core/Plugins/PluginInfo.cs index 5037b186b..7358c3ad5 100644 --- a/src/Artemis.Core/Plugins/PluginInfo.cs +++ b/src/Artemis.Core/Plugins/PluginInfo.cs @@ -152,6 +152,19 @@ namespace Artemis.Core internal set => SetAndNotify(ref _plugin, value); } + /// + /// Gets a string representing either a full path pointing to an svg or the markdown icon + /// + public string? ResolvedIcon + { + get + { + if (Icon == null) + return null; + return Icon.EndsWith(".svg") ? Plugin.ResolveRelativePath(Icon) : Icon; + } + } + internal string PreferredPluginDirectory => $"{Main.Split(".dll")[0].Replace("/", "").Replace("\\", "")}-{Guid.ToString().Substring(0, 8)}"; /// diff --git a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DownloadFileAction.cs b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DownloadFileAction.cs index 0707abfd6..8d09f4766 100644 --- a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DownloadFileAction.cs +++ b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DownloadFileAction.cs @@ -26,10 +26,29 @@ namespace Artemis.Core ShowProgressBar = true; } + /// + /// Creates a new instance of a copy folder action + /// + /// The name of the action + /// A function returning the URL to download + /// The target file to save as (will be created if needed) + public DownloadFileAction(string name, Func> urlFunction, string fileName) : base(name) + { + UrlFunction = urlFunction ?? throw new ArgumentNullException(nameof(urlFunction)); + FileName = fileName ?? throw new ArgumentNullException(nameof(fileName)); + + ShowProgressBar = true; + } + /// /// Gets the source URL to download /// - public string Url { get; } + public string? Url { get; } + + /// + /// Gets the function returning the URL to download + /// + public Func>? UrlFunction { get; } /// /// Gets the target file to save as (will be created if needed) @@ -41,19 +60,25 @@ namespace Artemis.Core { using HttpClient client = new(); await using FileStream destinationStream = new(FileName, FileMode.OpenOrCreate); + string? url = Url; + if (url is null) + { + Status = "Retrieving download URL"; + url = await UrlFunction!(); + } void ProgressOnProgressReported(object? sender, EventArgs e) { if (Progress.ProgressPerSecond != 0) - Status = $"Downloading {Url} - {Progress.ProgressPerSecond.Bytes().Humanize("#.##")}/sec"; + Status = $"Downloading {url} - {Progress.ProgressPerSecond.Bytes().Humanize("#.##")}/sec"; else - Status = $"Downloading {Url}"; + Status = $"Downloading {url}"; } Progress.ProgressReported += ProgressOnProgressReported; // Get the http headers first to examine the content length - using HttpResponseMessage response = await client.GetAsync(Url, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + using HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken); await using Stream download = await response.Content.ReadAsStreamAsync(cancellationToken); long? contentLength = response.Content.Headers.ContentLength; diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index 00eb627a0..027ac09af 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -529,7 +529,7 @@ namespace Artemis.Core.Services string targetDirectory = pluginInfo.PreferredPluginDirectory; if (Directory.Exists(Path.Combine(pluginDirectory.FullName, targetDirectory))) - throw new ArtemisPluginException($"A directory for this plugin already exists {Path.Combine(pluginDirectory.FullName, targetDirectory)}"); + Directory.Delete(Path.Combine(pluginDirectory.FullName, targetDirectory), true); // Extract everything in the same archive directory to the unique plugin directory DirectoryInfo directoryInfo = new(Path.Combine(pluginDirectory.FullName, targetDirectory)); diff --git a/src/Artemis.Core/Utilities/CorePluginFeature.cs b/src/Artemis.Core/Utilities/CorePluginFeature.cs index 6e7857389..979457645 100644 --- a/src/Artemis.Core/Utilities/CorePluginFeature.cs +++ b/src/Artemis.Core/Utilities/CorePluginFeature.cs @@ -1,4 +1,5 @@ -using Artemis.Core.LayerEffects; +using System.Collections.Generic; +using Artemis.Core.LayerEffects; using Artemis.Core.Modules; namespace Artemis.Core @@ -14,6 +15,8 @@ namespace Artemis.Core IsEnabled = true; } + public override List? ActivationRequirements => null; + public override void Enable() { } diff --git a/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs b/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs index 8c8de1110..81aa22772 100644 --- a/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs +++ b/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs @@ -41,6 +41,7 @@ namespace Artemis.Storage.Repositories public ProfileCategoryEntity IsUnique(string name, Guid? id) { + name = name.Trim(); if (id == null) return _repository.FirstOrDefault(p => p.Name == name); return _repository.FirstOrDefault(p => p.Name == name && p.Id != id.Value); diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs index 130cbe0c4..053fd0fdb 100644 --- a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs +++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs @@ -53,7 +53,6 @@ namespace Artemis.UI.Shared // Run an update timer at 25 fps _timer = new Timer(40); - _timer.Elapsed += TimerOnTick; MouseLeftButtonUp += OnMouseLeftButtonUp; Loaded += OnLoaded; @@ -158,7 +157,8 @@ namespace Artemis.UI.Shared /// protected virtual void Dispose(bool disposing) { - if (disposing) _timer.Stop(); + if (disposing) + _timer.Dispose(); } @@ -191,6 +191,7 @@ namespace Artemis.UI.Shared private void OnUnloaded(object? sender, RoutedEventArgs e) { _timer.Stop(); + _timer.Elapsed -= TimerOnTick; if (_oldDevice != null) { @@ -222,6 +223,7 @@ namespace Artemis.UI.Shared private void OnLoaded(object? sender, RoutedEventArgs e) { _timer.Start(); + _timer.Elapsed += TimerOnTick; } private void TimerOnTick(object? sender, EventArgs e) diff --git a/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.xaml b/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.xaml index b7a60b9c8..3611f5bef 100644 --- a/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.xaml +++ b/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.xaml @@ -11,6 +11,7 @@ d:DesignHeight="450" d:DesignWidth="800"> + @@ -47,10 +48,11 @@ - + diff --git a/src/Artemis.UI.Shared/Converters/StreamToBitmapImageConverter.cs b/src/Artemis.UI.Shared/Converters/StreamToBitmapImageConverter.cs index 6e8be2be0..1c2dc963b 100644 --- a/src/Artemis.UI.Shared/Converters/StreamToBitmapImageConverter.cs +++ b/src/Artemis.UI.Shared/Converters/StreamToBitmapImageConverter.cs @@ -8,7 +8,7 @@ namespace Artemis.UI.Shared { /// /// - /// Converts into . + /// Converts bitmap file in the form of a into . /// [ValueConversion(typeof(Stream), typeof(BitmapImage))] public class StreamToBitmapImageConverter : IValueConverter @@ -16,7 +16,7 @@ namespace Artemis.UI.Shared /// public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - if (value is not Stream stream) + if (value is not Stream stream) return null; stream.Position = 0; diff --git a/src/Artemis.UI.Shared/Converters/StreamToSvgImageConverter.cs b/src/Artemis.UI.Shared/Converters/StreamToSvgImageConverter.cs new file mode 100644 index 000000000..969f6d47f --- /dev/null +++ b/src/Artemis.UI.Shared/Converters/StreamToSvgImageConverter.cs @@ -0,0 +1,48 @@ +using System; +using System.Globalization; +using System.IO; +using System.Windows.Data; +using System.Windows.Media.Imaging; +using SharpVectors.Converters; +using SharpVectors.Renderers.Wpf; + +namespace Artemis.UI.Shared +{ + /// + /// + /// Converts SVG file in the form of a into . + /// + [ValueConversion(typeof(Stream), typeof(BitmapImage))] + public class StreamToSvgImageConverter : IValueConverter + { + /// + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is not Stream stream) + return null; + + stream.Position = 0; + + StreamSvgConverter converter = new(new WpfDrawingSettings()); + using MemoryStream imageStream = new(); + converter.Convert(stream, imageStream); + + BitmapImage selectedBitmap = new(); + selectedBitmap.BeginInit(); + selectedBitmap.StreamSource = imageStream; + selectedBitmap.CacheOption = BitmapCacheOption.OnLoad; + selectedBitmap.EndInit(); + selectedBitmap.Freeze(); + + stream.Position = 0; + return selectedBitmap; + } + + + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return Binding.DoNothing; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Utilities/PluginUtilities.cs b/src/Artemis.UI.Shared/Utilities/PluginUtilities.cs deleted file mode 100644 index c393c7fdd..000000000 --- a/src/Artemis.UI.Shared/Utilities/PluginUtilities.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.IO; -using Artemis.Core; -using MaterialDesignThemes.Wpf; - -namespace Artemis.UI.Shared -{ - /// - /// Provides utilities for UI-related plugin tasks - /// - public static class PluginUtilities - { - /// - /// Transforms the provided icon so that it is usable by the control - /// - /// The plugin the icon belongs to - /// - /// The icon, may be a string representation of a or a relative path - /// pointing to a .svg file - /// - /// - public static object GetPluginIcon(Plugin plugin, string icon) - { - if (icon == null) - return PackIconKind.QuestionMarkCircle; - - // Icon is provided as a path - if (icon.EndsWith(".svg")) - { - string iconPath = plugin.ResolveRelativePath(icon); - if (!File.Exists(iconPath)) - return PackIconKind.QuestionMarkCircle; - return iconPath; - } - - // Icon is provided as string to avoid having to reference MaterialDesignThemes - bool parsedIcon = Enum.TryParse(icon, true, out PackIconKind iconEnum); - if (parsedIcon == false) - iconEnum = PackIconKind.QuestionMarkCircle; - return iconEnum; - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Converters/ValuesAdditionConverter.cs b/src/Artemis.UI/Converters/ValuesAdditionConverter.cs new file mode 100644 index 000000000..9cda8c99a --- /dev/null +++ b/src/Artemis.UI/Converters/ValuesAdditionConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Windows.Data; + +namespace Artemis.UI.Converters +{ + public class ValuesAdditionConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + return values.Where(v => v is double).Cast().Sum(); + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.xaml index ebf357f9e..4371df56c 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.xaml @@ -11,7 +11,7 @@ - + - + \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.xaml index aa4e288d3..75b8ae201 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/BrushPropertyInputView.xaml @@ -18,7 +18,7 @@ - + - + \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.xaml index ea3f4a60f..02ffaad0a 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.xaml @@ -10,14 +10,14 @@ d:DesignHeight="25" d:DesignWidth="800" d:DataContext="{d:DesignInstance propertyInput:ColorGradientPropertyInputViewModel}"> - + - + DialogHost="PropertyTreeDialogHost" + ToolTip="Click to edit gradient colors" /> + \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.xaml index 818914562..53c5b2da3 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.xaml @@ -7,7 +7,7 @@ mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - + - + \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.xaml index f3d4a2b4a..784c7bea2 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.xaml @@ -11,7 +11,7 @@ d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance propertyInput:FloatPropertyInputViewModel}"> - + - + IsEnabled="{Binding IsEnabled}" /> + \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.xaml index aabe66a33..c2c5d3501 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatRangePropertyInputView.xaml @@ -5,11 +5,12 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:s="https://github.com/canton7/Stylet" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" + xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" - d:DataContext="{d:DesignInstance local:FloatRangePropertyInputViewModel}"> + d:DataContext="{d:DesignInstance propertyInput:FloatRangePropertyInputViewModel}"> - + + IsEnabled="{Binding IsStartEnabled}" /> - - + IsEnabled="{Binding IsEndEnabled}" /> + \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.xaml index 4cae90771..d700033f8 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/IntPropertyInputView.xaml @@ -6,11 +6,12 @@ xmlns:s="https://github.com/canton7/Stylet" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" + xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance propertyInput:IntPropertyInputViewModel}"> - + - + IsEnabled="{Binding IsEnabled}" /> + \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.xaml index 40a0e815a..da9440ce1 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/IntRangePropertyInputView.xaml @@ -5,11 +5,12 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:s="https://github.com/canton7/Stylet" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" + xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" - d:DataContext="{d:DesignInstance local:IntRangePropertyInputViewModel}"> + d:DataContext="{d:DesignInstance propertyInput:IntRangePropertyInputViewModel}"> - + + IsEnabled="{Binding IsStartEnabled}" /> - - + IsEnabled="{Binding IsEndEnabled}" /> + \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.xaml index 6861259bb..026d4d381 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/SKColorPropertyInputView.xaml @@ -5,6 +5,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:artemis="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:s="https://github.com/canton7/Stylet" + xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput" mc:Ignorable="d" d:DesignHeight="25" d:DesignWidth="800" d:DataContext="{d:DesignInstance propertyInput:SKColorPropertyInputViewModel}"> @@ -12,7 +13,7 @@ - + - + \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.xaml index 1f15efc15..06cf966ca 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/SKPointPropertyInputView.xaml @@ -5,11 +5,12 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:s="https://github.com/canton7/Stylet" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" + xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput" mc:Ignorable="d" d:DesignHeight="25" d:DesignWidth="800" d:DataContext="{d:DesignInstance propertyInput:SKPointPropertyInputViewModel}"> - + + IsEnabled="{Binding IsXEnabled}" /> , - + IsEnabled="{Binding IsYEnabled}" /> + \ No newline at end of file diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.xaml index 0f5dfc93c..a15a2b38f 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.xaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/SKSizePropertyInputView.xaml @@ -5,11 +5,12 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:s="https://github.com/canton7/Stylet" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" + xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" - d:DataContext="{d:DesignInstance local:SKSizePropertyInputViewModel}"> + d:DataContext="{d:DesignInstance propertyInput:SKSizePropertyInputViewModel}"> - + + IsEnabled="{Binding IsHeightEnabled}" /> , - + IsEnabled="{Binding IsWidthEnabled}" /> + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerView.xaml b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerView.xaml index 67cf2e553..aa89223e3 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerView.xaml @@ -1,19 +1,19 @@  + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:s="https://github.com/canton7/Stylet" + xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" + xmlns:treeItem1="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem" + xmlns:Converters="clr-namespace:Artemis.UI.Converters" + xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" + x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem.LayerView" + mc:Ignorable="d" + d:DesignHeight="450" d:DesignWidth="800" + d:DataContext="{d:DesignInstance {x:Type treeItem1:LayerViewModel}}"> - + @@ -72,12 +72,17 @@ + + + Toggle suspended state + Shift+click to toggle focus + + diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs index 36b1cd5ab..2ae8702b1 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using System.Windows.Input; using Artemis.Core; using Artemis.Core.LayerBrushes; using Artemis.Core.Services; @@ -18,9 +19,9 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem { private readonly IDialogService _dialogService; private readonly ILayerBrushService _layerBrushService; - private readonly IRgbService _rgbService; private readonly IProfileEditorService _profileEditorService; private readonly IProfileTreeVmFactory _profileTreeVmFactory; + private readonly IRgbService _rgbService; private ProfileElement _profileElement; protected TreeItemViewModel(ProfileElement profileElement, @@ -243,6 +244,39 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem public void SuspendedToggled() { + // If shift is held toggle focus state + if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) + { + // Get all profile elements + List elements = ProfileElement.Profile.GetAllFolders().Cast().ToList(); + elements.AddRange(ProfileElement.Profile.GetAllLayers().Cast().ToList()); + + // Separate out the targets of the focus state, the current profile element and all its parents + List targets = ProfileElement.GetAllFolders().Cast().ToList(); + targets.AddRange(ProfileElement.GetAllLayers().Cast().ToList()); + ProfileElement target = ProfileElement; + while (target != null) + { + targets.Add(target); + target = target.Parent; + } + + // If any element is suspended, untoggle focus and unsuspend everything + if (elements.Except(targets).Any(e => e.Suspended)) + { + foreach (ProfileElement profileElement in elements) + profileElement.Suspended = false; + } + // Otherwise suspend everything except the targets + else + { + foreach (ProfileElement profileElement in elements.Except(targets)) + profileElement.Suspended = true; + foreach (ProfileElement profileElement in targets) + profileElement.Suspended = false; + } + } + _profileEditorService.SaveSelectedProfileConfiguration(); } diff --git a/src/Artemis.UI/Screens/RootView.xaml b/src/Artemis.UI/Screens/RootView.xaml index f4225eeac..6f251e706 100644 --- a/src/Artemis.UI/Screens/RootView.xaml +++ b/src/Artemis.UI/Screens/RootView.xaml @@ -23,58 +23,63 @@ KeyUp="{s:Action WindowKeyUp}" MouseDown="{s:Action WindowMouseDown}" MouseUp="{s:Action WindowMouseUp}" - d:DesignHeight="640" d:DesignWidth="1200" + d:DesignHeight="640" d:DesignWidth="1200" d:DataContext="{d:DesignInstance screens:RootViewModel}"> - - - - - - - - - - - - - - + + + + + + + - - - - - - - - - + - - - - - + + + + + + + + + + + + + + - - - + + + + + + + - + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/RootViewModel.cs b/src/Artemis.UI/Screens/RootViewModel.cs index c29653c02..9154a6329 100644 --- a/src/Artemis.UI/Screens/RootViewModel.cs +++ b/src/Artemis.UI/Screens/RootViewModel.cs @@ -61,6 +61,7 @@ namespace Artemis.UI.Screens _frameTimeUpdateTimer = new Timer(500); _windowSize = _settingsService.GetSetting("UI.RootWindowSize"); + SidebarWidth = _settingsService.GetSetting("UI.SidebarWidth", new GridLength(240)); SidebarViewModel = sidebarViewModel; SidebarViewModel.ConductWith(this); @@ -68,6 +69,7 @@ namespace Artemis.UI.Screens WindowTitle = $"Artemis {versionAttribute?.InformationalVersion} build {Constants.BuildInfo.BuildNumberDisplay}"; } + public PluginSetting SidebarWidth { get; } public SidebarViewModel SidebarViewModel { get; } public ISnackbarMessageQueue MainMessageQueue @@ -75,7 +77,7 @@ namespace Artemis.UI.Screens get => _mainMessageQueue; set => SetAndNotify(ref _mainMessageQueue, value); } - + public string WindowTitle { get => _windowTitle; @@ -210,7 +212,7 @@ namespace Artemis.UI.Screens _frameTimeUpdateTimer.Stop(); SidebarViewModel.SelectedScreenChanged -= SidebarViewModelOnSelectedScreenChanged; - + SidebarWidth.Save(); _windowSize.Value ??= new WindowSize(); _windowSize.Value.ApplyFromWindow(_window); _windowSize.Save(); diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs index 37c056fe8..32dbb01b5 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs @@ -142,7 +142,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs private void PopulateModules() { Modules.Clear(); - Modules.AddRange(_pluginManagementService.GetFeaturesOfType().Where(p => p.IsEnabled).OrderBy(m => m.DisplayName)); + Modules.AddRange(_pluginManagementService.GetFeaturesOfType().Where(p => p.IsEnabled).OrderBy(m => m.Info.Name)); if (SelectedModule == null) _dataModelUIService.UpdateModules(MainDataModel); diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugPluginView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugPluginView.xaml index 4a3d11955..538e7de06 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugPluginView.xaml +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugPluginView.xaml @@ -17,7 +17,7 @@ - + diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugPluginViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugPluginViewModel.cs index 719eff26b..52da00c64 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugPluginViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/Performance/PerformanceDebugPluginViewModel.cs @@ -1,29 +1,25 @@ using System.Linq; using Artemis.Core; -using Artemis.UI.Shared; using Stylet; namespace Artemis.UI.Screens.Settings.Debug.Tabs.Performance { public class PerformanceDebugPluginViewModel : Screen { - public Plugin Plugin { get; } - public object Icon { get; } - public PerformanceDebugPluginViewModel(Plugin plugin) { Plugin = plugin; - Icon = PluginUtilities.GetPluginIcon(Plugin, Plugin.Info.Icon); } + public Plugin Plugin { get; } + public BindableCollection Profilers { get; } = new(); + public void Update() { foreach (Profiler pluginProfiler in Plugin.Profilers.Where(p => p.Measurements.Any())) - { if (Profilers.All(p => p.Profiler != pluginProfiler)) Profilers.Add(new PerformanceDebugProfilerViewModel(pluginProfiler)); - } foreach (PerformanceDebugProfilerViewModel profilerViewModel in Profilers) profilerViewModel.Update(); diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml index bd8688b08..d18c02458 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml @@ -29,8 +29,9 @@ diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabViewModel.cs index fa9632187..67aa81fa1 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabViewModel.cs @@ -55,6 +55,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins if (!Items.Contains(pluginSettingsViewModel)) Items.Add(pluginSettingsViewModel); } + foreach (PluginSettingsViewModel pluginSettingsViewModel in Items.ToList()) { if (!instances.Contains(pluginSettingsViewModel)) @@ -76,21 +77,28 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins base.OnInitialActivate(); } - public void ImportPlugin() + public async Task ImportPlugin() { - VistaOpenFileDialog dialog = new(); - dialog.Filter = "ZIP files (*.zip)|*.zip"; - dialog.Title = "Import Artemis plugin"; + VistaOpenFileDialog dialog = new() {Filter = "ZIP files (*.zip)|*.zip", Title = "Import Artemis plugin"}; bool? result = dialog.ShowDialog(); - if (result == true) + if (result != true) + return; + + // Take the actual import off of the UI thread + await Task.Run(() => { Plugin plugin = _pluginManagementService.ImportPlugin(dialog.FileName); GetPluginInstances(); SearchPluginInput = plugin.Info.Name; + // Enable it via the VM to enable the prerequisite dialog + PluginSettingsViewModel pluginViewModel = Items.FirstOrDefault(i => i.Plugin == plugin); + if (pluginViewModel is {IsEnabled: false}) + pluginViewModel.IsEnabled = true; + _messageService.ShowMessage($"Imported plugin: {plugin.Info.Name}"); - } + }); } public void GetPluginInstances() diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml index 82cad706c..d20d17e5a 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsView.xaml @@ -42,7 +42,7 @@ - _enabling; set => SetAndNotify(ref _enabling, value); } - - public object Icon { get; set; } + public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name; public bool CanOpenSettings => IsEnabled && Plugin.ConfigurationDialog != null; @@ -102,7 +99,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins try { PluginConfigurationViewModel viewModel = (PluginConfigurationViewModel) Plugin.Kernel.Get(configurationViewModel.Type); - _windowManager.ShowWindow(new PluginSettingsWindowViewModel(viewModel, Icon)); + _windowManager.ShowWindow(new PluginSettingsWindowViewModel(viewModel)); } catch (Exception e) { diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsWindowViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsWindowViewModel.cs index 7f4a229f8..47230b878 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsWindowViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsWindowViewModel.cs @@ -8,10 +8,10 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins { private readonly PluginConfigurationViewModel _configurationViewModel; - public PluginSettingsWindowViewModel(PluginConfigurationViewModel configurationViewModel, object icon) + public PluginSettingsWindowViewModel(PluginConfigurationViewModel configurationViewModel) { _configurationViewModel = configurationViewModel ?? throw new ArgumentNullException(nameof(configurationViewModel)); - Icon = icon; + Icon = configurationViewModel.Plugin.Info.Icon; } public object Icon { get; } diff --git a/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileEdit/ModuleActivationRequirementsViewModel.cs b/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileEdit/ModuleActivationRequirementsViewModel.cs index 49a82f1d5..aadd20c74 100644 --- a/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileEdit/ModuleActivationRequirementsViewModel.cs +++ b/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileEdit/ModuleActivationRequirementsViewModel.cs @@ -36,7 +36,7 @@ namespace Artemis.UI.Screens.Sidebar.Dialogs.ProfileEdit : "any requirement is met"; Items.Clear(); - if (Module != null) + if (Module?.ActivationRequirements != null) Items.AddRange(Module.ActivationRequirements.Select(_sidebarVmFactory.ModuleActivationRequirementViewModel)); } } diff --git a/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileEdit/ProfileEditView.xaml b/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileEdit/ProfileEditView.xaml index b298b261b..9e279ca51 100644 --- a/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileEdit/ProfileEditView.xaml +++ b/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileEdit/ProfileEditView.xaml @@ -6,10 +6,8 @@ xmlns:s="https://github.com/canton7/Stylet" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:converters="clr-namespace:Artemis.UI.Converters" - xmlns:dialogs="clr-namespace:Artemis.UI.Screens.Sidebar.Dialogs" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:profileEdit="clr-namespace:Artemis.UI.Screens.Sidebar.Dialogs.ProfileEdit" - xmlns:svgc="http://sharpvectors.codeplex.com/svgc/" xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="280" @@ -17,9 +15,10 @@ Width="800"> - - + + + @@ -112,15 +111,16 @@ - - -