From dab11cb3e767554c3dc37d7a38328f758217a4b0 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Wed, 29 Jul 2020 00:29:05 +0200 Subject: [PATCH] Profile editor - Fixed new profile elements not always appearing UI - Fixed all the memory leaks I could find, closing the UI now frees a lot of RAM Noise brush - Decreased memory usage by removing unused 4D noise Layout editor - Fixed device properties not always applying --- .../Models/Profile/ProfileElement.cs | 21 ++ .../Controls/DeviceVisualizer.cs | 29 ++- .../Services/ProfileEditorService.cs | 66 +++--- .../Converters/InverseBooleanConverter.cs | 13 +- .../EnumPropertyInputViewModel.cs | 5 +- .../DisplayConditionGroupView.xaml | 29 +-- .../DisplayConditionGroupViewModel.cs | 4 +- .../DisplayConditionPredicateView.xaml | 19 +- .../DisplayConditionPredicateViewModel.cs | 4 +- .../DisplayConditionsView.xaml | 39 ++-- .../DisplayConditionsViewModel.cs | 18 +- .../Abstract/LayerPropertyBaseViewModel.cs | 6 +- .../LayerPropertiesViewModel.cs | 14 +- .../Timeline/TimelineKeyframeViewModel.cs | 12 + .../Timeline/TimelineSegmentViewModel.cs | 6 +- .../Timeline/TimelineViewModel.cs | 36 ++- .../ProfileTree/ProfileTreeViewModel.cs | 14 +- .../ProfileTree/TreeItem/TreeItemViewModel.cs | 32 ++- src/Artemis.UI/Screens/RootViewModel.cs | 76 +++++- .../Debug/Tabs/DataModelDebugViewModel.cs | 13 +- .../Screens/Sidebar/SidebarViewModel.cs | 12 +- .../Screens/Splash/SplashViewModel.cs | 2 + .../Dialogs/SurfaceDeviceConfigView.xaml | 20 +- .../NoiseBrush.cs | 10 +- .../Utilities/OpenSimplexNoise.cs | 217 +----------------- 25 files changed, 336 insertions(+), 381 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs index fcdf4bd97..79588e4ae 100644 --- a/src/Artemis.Core/Models/Profile/ProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs @@ -146,6 +146,8 @@ namespace Artemis.Core.Models.Profile child.Parent = this; } + + OnChildAdded(); } /// @@ -164,6 +166,8 @@ namespace Artemis.Core.Models.Profile child.Parent = null; } + + OnChildRemoved(); } public override string ToString() @@ -175,5 +179,22 @@ namespace Artemis.Core.Models.Profile /// Applies the profile element's properties to the underlying storage entity /// internal abstract void ApplyToEntity(); + + #region Events + + public event EventHandler ChildAdded; + public event EventHandler ChildRemoved; + + protected virtual void OnChildAdded() + { + ChildAdded?.Invoke(this, EventArgs.Empty); + } + + protected virtual void OnChildRemoved() + { + ChildRemoved?.Invoke(this, EventArgs.Empty); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs index c1271a780..6f7ac96c2 100644 --- a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs +++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs @@ -3,15 +3,12 @@ using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; -using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Threading; using Artemis.Core.Models.Surface; -using RGB.NET.Core; -using Size = System.Windows.Size; namespace Artemis.UI.Shared.Controls { @@ -28,9 +25,9 @@ namespace Artemis.UI.Shared.Controls private readonly DrawingGroup _backingStore; private readonly List _deviceVisualizerLeds; + private readonly DispatcherTimer _timer; private BitmapImage _deviceImage; private ArtemisDevice _oldDevice; - private readonly DispatcherTimer _timer; public DeviceVisualizer() { @@ -41,8 +38,8 @@ namespace Artemis.UI.Shared.Controls _timer = new DispatcherTimer {Interval = TimeSpan.FromMilliseconds(40)}; _timer.Tick += TimerOnTick; - Loaded += (sender, args) => _timer.Start(); - Unloaded += (sender, args) => _timer.Stop(); + Loaded += OnLoaded; + Unloaded += OnUnloaded; } public ArtemisDevice Device @@ -117,12 +114,28 @@ namespace Artemis.UI.Shared.Controls return rotationRect.Size; } + private void OnUnloaded(object sender, RoutedEventArgs e) + { + _timer.Stop(); + + if (_oldDevice != null) + { + Device.RgbDevice.PropertyChanged -= DevicePropertyChanged; + _oldDevice = null; + } + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + _timer.Start(); + } + private void TimerOnTick(object sender, EventArgs e) { if (ShowColors && Visibility == Visibility.Visible) Render(); } - + private void UpdateTransform() { InvalidateVisual(); @@ -204,7 +217,7 @@ namespace Artemis.UI.Shared.Controls if (e.PropertyName == nameof(Device.RgbDevice.Scale) || e.PropertyName == nameof(Device.RgbDevice.Rotation)) UpdateTransform(); } - + private void Render() { diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs index 1d09730f5..4c4d05455 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs @@ -23,8 +23,9 @@ namespace Artemis.UI.Shared.Services private readonly ILogger _logger; private readonly List _registeredPropertyEditors; private TimeSpan _currentTime; - private TimeSpan _lastUpdateTime; private int _pixelsPerSecond; + private object _selectedProfileLock = new object(); + private object _selectedProfileElementLock = new object(); public ProfileEditorService(ICoreService coreService, IProfileService profileService, IKernel kernel, ILogger logger) { @@ -69,43 +70,55 @@ namespace Artemis.UI.Shared.Services public void ChangeSelectedProfile(Profile profile) { - if (SelectedProfile == profile) - return; + lock (_selectedProfileLock) + { + if (SelectedProfile == profile) + return; - _logger.Verbose("ChangeSelectedProfile {profile}", profile); - ChangeSelectedProfileElement(null); + _logger.Verbose("ChangeSelectedProfile {profile}", profile); + ChangeSelectedProfileElement(null); - var profileElementEvent = new ProfileEventArgs(profile, SelectedProfile); - SelectedProfile = profile; - UpdateProfilePreview(); - OnSelectedProfileChanged(profileElementEvent); + var profileElementEvent = new ProfileEventArgs(profile, SelectedProfile); + SelectedProfile = profile; + UpdateProfilePreview(); + OnSelectedProfileChanged(profileElementEvent); + } } public void UpdateSelectedProfile() { - _logger.Verbose("UpdateSelectedProfile {profile}", SelectedProfile); - _profileService.UpdateProfile(SelectedProfile, true); - UpdateProfilePreview(); - OnSelectedProfileChanged(new ProfileEventArgs(SelectedProfile)); + lock (_selectedProfileLock) + { + _logger.Verbose("UpdateSelectedProfile {profile}", SelectedProfile); + _profileService.UpdateProfile(SelectedProfile, true); + UpdateProfilePreview(); + OnSelectedProfileChanged(new ProfileEventArgs(SelectedProfile)); + } } public void ChangeSelectedProfileElement(RenderProfileElement profileElement) { - if (SelectedProfileElement == profileElement) - return; + lock (_selectedProfileElementLock) + { + if (SelectedProfileElement == profileElement) + return; - _logger.Verbose("ChangeSelectedProfileElement {profile}", profileElement); - var profileElementEvent = new RenderProfileElementEventArgs(profileElement, SelectedProfileElement); - SelectedProfileElement = profileElement; - OnSelectedProfileElementChanged(profileElementEvent); + _logger.Verbose("ChangeSelectedProfileElement {profile}", profileElement); + var profileElementEvent = new RenderProfileElementEventArgs(profileElement, SelectedProfileElement); + SelectedProfileElement = profileElement; + OnSelectedProfileElementChanged(profileElementEvent); + } } public void UpdateSelectedProfileElement() { - _logger.Verbose("UpdateSelectedProfileElement {profile}", SelectedProfileElement); - _profileService.UpdateProfile(SelectedProfile, true); - UpdateProfilePreview(); - OnSelectedProfileElementUpdated(new RenderProfileElementEventArgs(SelectedProfileElement)); + lock (_selectedProfileElementLock) + { + _logger.Verbose("UpdateSelectedProfileElement {profile}", SelectedProfileElement); + _profileService.UpdateProfile(SelectedProfile, true); + UpdateProfilePreview(); + OnSelectedProfileElementUpdated(new RenderProfileElementEventArgs(SelectedProfileElement)); + } } public void UpdateProfilePreview() @@ -114,12 +127,11 @@ namespace Artemis.UI.Shared.Services return; // Stick to the main segment for any element that is not currently selected - foreach (var folder in SelectedProfile.GetAllFolders()) + foreach (var folder in SelectedProfile.GetAllFolders()) folder.OverrideProgress(CurrentTime, folder != SelectedProfileElement); - foreach (var layer in SelectedProfile.GetAllLayers()) + foreach (var layer in SelectedProfile.GetAllLayers()) layer.OverrideProgress(CurrentTime, layer != SelectedProfileElement); - _lastUpdateTime = CurrentTime; OnProfilePreviewUpdated(); } @@ -225,7 +237,7 @@ namespace Artemis.UI.Shared.Services { // Get all visible keyframes var keyframes = SelectedProfileElement.GetAllKeyframes() - .Where(k => SelectedProfileElement.IsPropertyGroupExpanded(k.BaseLayerProperty.Parent)) + .Where(k => k != excludedKeyframe && SelectedProfileElement.IsPropertyGroupExpanded(k.BaseLayerProperty.Parent)) .ToList(); // Find the closest keyframe diff --git a/src/Artemis.UI/Converters/InverseBooleanConverter.cs b/src/Artemis.UI/Converters/InverseBooleanConverter.cs index 2052a727b..ae7598871 100644 --- a/src/Artemis.UI/Converters/InverseBooleanConverter.cs +++ b/src/Artemis.UI/Converters/InverseBooleanConverter.cs @@ -9,8 +9,7 @@ namespace Artemis.UI.Converters { #region IValueConverter Members - public object Convert(object value, Type targetType, object parameter, - CultureInfo culture) + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (targetType == typeof(bool)) return !(bool) value; @@ -20,10 +19,14 @@ namespace Artemis.UI.Converters throw new InvalidOperationException("The target must be a boolean"); } - public object ConvertBack(object value, Type targetType, object parameter, - CultureInfo culture) + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { - throw new NotSupportedException(); + if (targetType == typeof(bool)) + return !(bool) value; + if (targetType == typeof(bool?)) + return !(bool?) value; + + throw new InvalidOperationException("The target must be a boolean"); } #endregion diff --git a/src/Artemis.UI/PropertyInput/EnumPropertyInputViewModel.cs b/src/Artemis.UI/PropertyInput/EnumPropertyInputViewModel.cs index e52a177e5..707efcdd2 100644 --- a/src/Artemis.UI/PropertyInput/EnumPropertyInputViewModel.cs +++ b/src/Artemis.UI/PropertyInput/EnumPropertyInputViewModel.cs @@ -4,6 +4,7 @@ using Artemis.Core.Models.Profile.LayerProperties; using Artemis.UI.Shared.PropertyInput; using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Utilities; +using Stylet; namespace Artemis.UI.PropertyInput { @@ -11,9 +12,9 @@ namespace Artemis.UI.PropertyInput { public EnumPropertyInputViewModel(LayerProperty layerProperty, IProfileEditorService profileEditorService) : base(layerProperty, profileEditorService) { - EnumValues = EnumUtilities.GetAllValuesAndDescriptions(typeof(T)); + EnumValues = new BindableCollection(EnumUtilities.GetAllValuesAndDescriptions(typeof(T))); } - public IEnumerable EnumValues { get; } + public BindableCollection EnumValues { get; } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupView.xaml index c1c22c6be..5b8312291 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupView.xaml @@ -13,30 +13,11 @@ - - - - + @@ -127,7 +108,13 @@ - + + + + + + + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupViewModel.cs index 3d64c4334..eb06ec9b2 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionGroupViewModel.cs @@ -70,7 +70,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions // Remove VMs of effects no longer applied on the layer var toRemove = Children.Where(c => !DisplayConditionGroup.Children.Contains(c.Model)).ToList(); - Children.RemoveRange(toRemove); + // Using RemoveRange breaks our lovely animations + foreach (var displayConditionViewModel in toRemove) + Children.Remove(displayConditionViewModel); foreach (var childModel in Model.Children) { diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml index 9b451960c..c83604a27 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml @@ -20,28 +20,11 @@ - - + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs index c6770f62d..8a46eb594 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateViewModel.cs @@ -171,13 +171,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions LeftSideDataModel.UpdateRequested += LeftDataModelUpdateRequested; RightSideDataModel.UpdateRequested += RightDataModelUpdateRequested; - IsInitialized = true; Update(); + IsInitialized = true; } public override void Update() { - if (!IsInitialized) + if (LeftSideDataModel == null || (DisplayConditionPredicate.PredicateType == PredicateType.Dynamic && RightSideDataModel == null)) return; // If static, only allow selecting properties also supported by input diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml index aa2ab3d81..fec4b6127 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsView.xaml @@ -6,10 +6,14 @@ xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:s="https://github.com/canton7/Stylet" + xmlns:converters="clr-namespace:Artemis.UI.Converters" x:Class="Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions.DisplayConditionsView" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance {x:Type local:DisplayConditionsViewModel}}"> + + + @@ -29,10 +33,13 @@ - When conditions no longer met - - - + On end: + + + + @@ -40,16 +47,18 @@ - + - - + + WAIT FOR FINISH - - - + + + @@ -57,15 +66,15 @@ - + - - + + SKIP - - + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs index 16445dc45..1ff24cd0a 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionsViewModel.cs @@ -1,9 +1,11 @@ -using Artemis.Core.Models.Profile; +using System.Threading.Tasks; +using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile.Conditions; using Artemis.Storage.Entities.Profile.Abstract; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared.Events; using Artemis.UI.Shared.Services.Interfaces; +using Stylet; namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions { @@ -18,7 +20,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions { _profileEditorService = profileEditorService; _displayConditionsVmFactory = displayConditionsVmFactory; - profileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected; + } public DisplayConditionGroupViewModel RootGroup @@ -42,8 +44,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions return; RenderProfileElement.AlwaysFinishTimeline = value == 0; - NotifyOfPropertyChange(nameof(ConditionBehaviourIndex)); - _profileEditorService.UpdateSelectedProfileElement(); } } @@ -70,5 +70,15 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions RootGroup.IsRootGroup = true; RootGroup.Update(); } + + protected override void OnActivate() + { + _profileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected; + } + + protected override void OnDeactivate() + { + _profileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected; + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs index 75613d333..6cfb11aed 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Abstract/LayerPropertyBaseViewModel.cs @@ -8,11 +8,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract public abstract class LayerPropertyBaseViewModel : PropertyChangedBase, IDisposable { private bool _isExpanded; - private List _children; + private BindableCollection _children; protected LayerPropertyBaseViewModel() { - Children = new List(); + Children = new BindableCollection(); } public abstract bool IsVisible { get; } @@ -23,7 +23,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract set => SetAndNotify(ref _isExpanded, value); } - public List Children + public BindableCollection Children { get => _children; set => SetAndNotify(ref _children, value); diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index c708e5fd1..bccd7ee86 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -164,6 +164,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; PopulateProperties(null); + + TimelineViewModel?.Dispose(); + TimelineViewModel = null; + StartTimelineSegmentViewModel?.Dispose(); + StartTimelineSegmentViewModel = null; + MainTimelineSegmentViewModel?.Dispose(); + MainTimelineSegmentViewModel = null; + EndTimelineSegmentViewModel?.Dispose(); + EndTimelineSegmentViewModel = null; + base.OnClose(); } @@ -256,9 +266,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties ApplyLayerBrush(); ApplyEffects(); } - - - + private void SelectedLayerOnLayerBrushUpdated(object sender, EventArgs e) { ApplyLayerBrush(); diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs index e18cc0520..a9549b4f2 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs @@ -31,6 +31,18 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline LayerPropertyKeyframe.EasingFunction, LayerPropertyKeyframe.LayerProperty ); + // If possible, shift the keyframe to the right by 11 pixels + var desiredPosition = newKeyframe.Position + TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 11); + if (desiredPosition <= newKeyframe.LayerProperty.ProfileElement.TimelineLength) + newKeyframe.Position = desiredPosition; + // Otherwise if possible shift it to the left by 11 pixels + else + { + desiredPosition = newKeyframe.Position - TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 11); + if (desiredPosition > TimeSpan.Zero) + newKeyframe.Position = desiredPosition; + } + LayerPropertyKeyframe.LayerProperty.AddKeyframe(newKeyframe); _profileEditorService.UpdateSelectedProfileElement(); } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs index cf9aad4ad..7af3c4d06 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineSegmentViewModel.cs @@ -140,14 +140,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline if (Segment == SegmentViewModelType.Start) { // Remove keyframes that fall in this segment - foreach (var baseLayerPropertyKeyframe in keyframes.Where(k => k.Position < startSegmentEnd)) + foreach (var baseLayerPropertyKeyframe in keyframes.Where(k => k.Position <= startSegmentEnd)) baseLayerPropertyKeyframe.Remove(); SelectedProfileElement.StartSegmentLength = TimeSpan.Zero; } else if (Segment == SegmentViewModelType.Main) { // Remove keyframes that fall in this segment - foreach (var baseLayerPropertyKeyframe in keyframes.Where(k => k.Position > startSegmentEnd && k.Position < mainSegmentEnd)) + foreach (var baseLayerPropertyKeyframe in keyframes.Where(k => k.Position > startSegmentEnd && k.Position <= mainSegmentEnd)) baseLayerPropertyKeyframe.Remove(); SelectedProfileElement.MainSegmentLength = TimeSpan.Zero; } @@ -189,7 +189,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline else if (Segment == SegmentViewModelType.End) segmentEnd = SelectedProfileElement.TimelineLength; - foreach (var baseLayerPropertyKeyframe in SelectedProfileElement.GetAllKeyframes().Where(k => k.Position >= segmentEnd)) + foreach (var baseLayerPropertyKeyframe in SelectedProfileElement.GetAllKeyframes().Where(k => k.Position > segmentEnd)) baseLayerPropertyKeyframe.Position += amount; } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs index dc7d0d17c..8fcf57eaf 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs @@ -7,6 +7,7 @@ using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; +using Artemis.Core.Models.Profile; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Utilities; @@ -27,13 +28,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline LayerPropertyGroups = layerPropertyGroups; SelectionRectangle = new RectangleGeometry(); + SelectedProfileElement = layerPropertiesViewModel.SelectedProfileElement; - _profileEditorService.SelectedProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged; + SelectedProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged; _profileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged; Update(); } + public RenderProfileElement SelectedProfileElement { get; set; } + public BindableCollection LayerPropertyGroups { get; } public RectangleGeometry SelectionRectangle @@ -42,21 +46,21 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline set => SetAndNotify(ref _selectionRectangle, value); } - public double StartSegmentWidth => _profileEditorService.PixelsPerSecond * _profileEditorService.SelectedProfileElement?.StartSegmentLength.TotalSeconds ?? 0; + public double StartSegmentWidth => _profileEditorService.PixelsPerSecond * SelectedProfileElement.StartSegmentLength.TotalSeconds; public double StartSegmentEndPosition => StartSegmentWidth; - public double MainSegmentWidth => _profileEditorService.PixelsPerSecond * _profileEditorService.SelectedProfileElement?.MainSegmentLength.TotalSeconds ?? 0; + public double MainSegmentWidth => _profileEditorService.PixelsPerSecond * SelectedProfileElement.MainSegmentLength.TotalSeconds; public double MainSegmentEndPosition => StartSegmentWidth + MainSegmentWidth; - public double EndSegmentWidth => _profileEditorService.PixelsPerSecond * _profileEditorService.SelectedProfileElement?.EndSegmentLength.TotalSeconds ?? 0; + public double EndSegmentWidth => _profileEditorService.PixelsPerSecond * SelectedProfileElement.EndSegmentLength.TotalSeconds; public double EndSegmentEndPosition => StartSegmentWidth + MainSegmentWidth + EndSegmentWidth; - public double TotalTimelineWidth => _profileEditorService.PixelsPerSecond * _profileEditorService.SelectedProfileElement?.TimelineLength.TotalSeconds ?? 0; + public double TotalTimelineWidth => _profileEditorService.PixelsPerSecond * SelectedProfileElement.TimelineLength.TotalSeconds; - public bool StartSegmentEnabled => _profileEditorService.SelectedProfileElement?.StartSegmentLength != TimeSpan.Zero; - public bool EndSegmentEnabled => _profileEditorService.SelectedProfileElement?.EndSegmentLength != TimeSpan.Zero; + public bool StartSegmentEnabled => SelectedProfileElement.StartSegmentLength != TimeSpan.Zero; + public bool EndSegmentEnabled => SelectedProfileElement.EndSegmentLength != TimeSpan.Zero; public void Dispose() { _profileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; - _profileEditorService.SelectedProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged; + SelectedProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged; } private void SelectedProfileElementOnPropertyChanged(object sender, PropertyChangedEventArgs e) @@ -115,8 +119,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline public void KeyframeMouseDown(object sender, MouseButtonEventArgs e) { - if (e.LeftButton == MouseButtonState.Released) - return; + // if (e.LeftButton == MouseButtonState.Released) + // return; var viewModel = (sender as Ellipse)?.DataContext as TimelineKeyframeViewModel; if (viewModel == null) @@ -220,6 +224,18 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected)) keyframeViewModel.SaveOffsetToKeyframe(sourceKeyframeViewModel); + if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) + { + cursorTime = _profileEditorService.SnapToTimeline( + cursorTime, + TimeSpan.FromMilliseconds(1000f / _profileEditorService.PixelsPerSecond * 5), + true, + false, + true, + sourceKeyframeViewModel.BaseLayerPropertyKeyframe + ); + } + sourceKeyframeViewModel.UpdatePosition(cursorTime); foreach (var keyframeViewModel in keyframeViewModels.Where(k => k.IsSelected)) diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs index 247afd9df..4009ab0f7 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs @@ -5,6 +5,7 @@ using System.Windows; using Artemis.Core.Models.Profile; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem; +using Artemis.UI.Shared.Events; using Artemis.UI.Shared.Services.Interfaces; using GongSolutions.Wpf.DragDrop; using Stylet; @@ -109,6 +110,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree return; } + RootFolder?.Dispose(); RootFolder = _folderVmFactory.Create(folder); _updatingTree = false; @@ -168,9 +170,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree #region Event handlers - private void OnProfileElementSelected(object sender, EventArgs e) + private void OnProfileElementSelected(object sender, RenderProfileElementEventArgs e) { - if (_profileEditorService.SelectedProfileElement == SelectedTreeItem?.ProfileElement) + if (e.RenderProfileElement == SelectedTreeItem?.ProfileElement) return; if (RootFolder == null) @@ -182,13 +184,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree _updatingTree = true; RootFolder.UpdateProfileElements(); _updatingTree = false; - if (_profileEditorService.SelectedProfileElement == null) + if (e.RenderProfileElement == null) SelectedTreeItem = null; else { - var vms = RootFolder.GetAllChildren(); - vms.Add(RootFolder); - SelectedTreeItem = vms.FirstOrDefault(vm => vm.ProfileElement == _profileEditorService.SelectedProfileElement); + var match = RootFolder.GetAllChildren().FirstOrDefault(vm => vm.ProfileElement == e.RenderProfileElement); + if (match != null) + SelectedTreeItem = match; } } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs index f360b8c6f..04d32a845 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs @@ -16,10 +16,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem { private readonly IDialogService _dialogService; private readonly IFolderVmFactory _folderVmFactory; - private readonly IRenderElementService _renderElementService; private readonly ILayerVmFactory _layerVmFactory; private readonly IProfileEditorService _profileEditorService; - private BindableCollection _children; + private readonly IRenderElementService _renderElementService; private TreeItemViewModel _parent; private ProfileElement _profileElement; @@ -41,6 +40,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem ProfileElement = profileElement; Children = new BindableCollection(); + + ProfileElement.ChildAdded += ProfileElementOnChildAdded; + ProfileElement.ChildRemoved += ProfileElementOnChildRemoved; + UpdateProfileElements(); } @@ -56,16 +59,15 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem set => SetAndNotify(ref _profileElement, value); } - public BindableCollection Children - { - get => _children; - set => SetAndNotify(ref _children, value); - } + public BindableCollection Children { get; } public abstract bool SupportsChildren { get; } public void Dispose() { + ProfileElement.ChildAdded -= ProfileElementOnChildAdded; + ProfileElement.ChildRemoved -= ProfileElementOnChildRemoved; + Dispose(true); GC.SuppressFinalize(this); } @@ -94,7 +96,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem Parent.ProfileElement.RemoveChild(source.ProfileElement); Parent.ProfileElement.AddChild(source.ProfileElement, ProfileElement.Order); - Parent.UpdateProfileElements(); } public void SetElementBehind(TreeItemViewModel source) @@ -107,7 +108,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem Parent.ProfileElement.RemoveChild(source.ProfileElement); Parent.ProfileElement.AddChild(source.ProfileElement, ProfileElement.Order + 1); - Parent.UpdateProfileElements(); } public void RemoveExistingElement(TreeItemViewModel treeItem) @@ -118,6 +118,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem ProfileElement.RemoveChild(treeItem.ProfileElement); Children.Remove(treeItem); treeItem.Parent = null; + treeItem.Dispose(); } public void AddExistingElement(TreeItemViewModel treeItem) @@ -136,7 +137,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem throw new ArtemisUIException("Cannot add a folder to a profile element of type " + ProfileElement.GetType().Name); ProfileElement.AddChild(new Folder(ProfileElement.Profile, ProfileElement, "New folder")); - UpdateProfileElements(); _profileEditorService.UpdateSelectedProfile(); } @@ -146,7 +146,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem throw new ArtemisUIException("Cannot add a layer to a profile element of type " + ProfileElement.GetType().Name); _renderElementService.CreateLayer(ProfileElement.Profile, ProfileElement, "New layer"); - UpdateProfileElements(); _profileEditorService.UpdateSelectedProfile(); } @@ -182,7 +181,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem var parent = Parent; ProfileElement.Parent?.RemoveChild(ProfileElement); parent.RemoveExistingElement(this); - parent.UpdateProfileElements(); _profileEditorService.UpdateSelectedProfile(); } @@ -236,5 +234,15 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem { } } + + private void ProfileElementOnChildRemoved(object sender, EventArgs e) + { + UpdateProfileElements(); + } + + private void ProfileElementOnChildAdded(object sender, EventArgs e) + { + UpdateProfileElements(); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/RootViewModel.cs b/src/Artemis.UI/Screens/RootViewModel.cs index 7beeacf04..88427dc2e 100644 --- a/src/Artemis.UI/Screens/RootViewModel.cs +++ b/src/Artemis.UI/Screens/RootViewModel.cs @@ -1,4 +1,5 @@ -using System.ComponentModel; +using System; +using System.ComponentModel; using System.Reflection; using System.Threading.Tasks; using System.Timers; @@ -21,45 +22,50 @@ namespace Artemis.UI.Screens { public class RootViewModel : Conductor { + private readonly IRegistrationService _builtInRegistrationService; + private readonly ISnackbarMessageQueue _snackbarMessageQueue; private readonly PluginSetting _colorScheme; private readonly ICoreService _coreService; private readonly IDebugService _debugService; - private readonly IRegistrationService _builtInRegistrationService; private readonly IEventAggregator _eventAggregator; private readonly ThemeWatcher _themeWatcher; private readonly Timer _titleUpdateTimer; private readonly PluginSetting _windowSize; - private bool _lostFocus; - private bool _isSidebarVisible; private bool _activeItemReady; + private bool _isSidebarVisible; + private bool _lostFocus; private string _windowTitle; + private ISnackbarMessageQueue _mainMessageQueue; public RootViewModel(IEventAggregator eventAggregator, SidebarViewModel sidebarViewModel, ISettingsService settingsService, ICoreService coreService, IDebugService debugService, IRegistrationService builtInRegistrationService, ISnackbarMessageQueue snackbarMessageQueue) { SidebarViewModel = sidebarViewModel; - MainMessageQueue = snackbarMessageQueue; _eventAggregator = eventAggregator; _coreService = coreService; _debugService = debugService; _builtInRegistrationService = builtInRegistrationService; + _snackbarMessageQueue = snackbarMessageQueue; _titleUpdateTimer = new Timer(500); - _titleUpdateTimer.Elapsed += (sender, args) => UpdateWindowTitle(); + _colorScheme = settingsService.GetSetting("UI.ColorScheme", ApplicationColorScheme.Automatic); _windowSize = settingsService.GetSetting("UI.RootWindowSize"); - _colorScheme.SettingChanged += (sender, args) => ApplyColorSchemeSetting(); + _themeWatcher = new ThemeWatcher(); - _themeWatcher.ThemeChanged += (sender, args) => ApplyWindowsTheme(args.Theme); ApplyColorSchemeSetting(); ActiveItem = SidebarViewModel.SelectedItem; ActiveItemReady = true; - SidebarViewModel.PropertyChanged += SidebarViewModelOnPropertyChanged; } public SidebarViewModel SidebarViewModel { get; } - public ISnackbarMessageQueue MainMessageQueue { get; } + + public ISnackbarMessageQueue MainMessageQueue + { + get => _mainMessageQueue; + set => SetAndNotify(ref _mainMessageQueue, value); + } public bool IsSidebarVisible { @@ -112,6 +118,7 @@ namespace Artemis.UI.Screens { _eventAggregator.Publish(new MainWindowKeyEvent(sender, false, e)); } + public void WindowMouseDown(object sender, MouseButtonEventArgs e) { _eventAggregator.Publish(new MainWindowMouseEvent(sender, true, e)); @@ -176,6 +183,21 @@ namespace Artemis.UI.Screens extensionsPaletteHelper.SetLightDark(colorScheme == ApplicationColorScheme.Dark); } + private void OnTitleUpdateTimerOnElapsed(object sender, ElapsedEventArgs args) + { + UpdateWindowTitle(); + } + + private void ThemeWatcherOnThemeChanged(object sender, WindowsThemeEventArgs e) + { + ApplyWindowsTheme(e.Theme); + } + + private void ColorSchemeOnSettingChanged(object sender, EventArgs e) + { + ApplyColorSchemeSetting(); + } + #region Overrides of Screen protected override void OnViewLoaded() @@ -194,22 +216,54 @@ namespace Artemis.UI.Screens protected override void OnActivate() { + MainMessageQueue = _snackbarMessageQueue; UpdateWindowTitle(); - _titleUpdateTimer.Start(); _builtInRegistrationService.RegisterBuiltInDataModelDisplays(); _builtInRegistrationService.RegisterBuiltInDataModelInputs(); _builtInRegistrationService.RegisterBuiltInPropertyEditors(); + + _titleUpdateTimer.Elapsed += OnTitleUpdateTimerOnElapsed; + _colorScheme.SettingChanged += ColorSchemeOnSettingChanged; + _themeWatcher.ThemeChanged += ThemeWatcherOnThemeChanged; + SidebarViewModel.PropertyChanged += SidebarViewModelOnPropertyChanged; + + _titleUpdateTimer.Start(); } protected override void OnDeactivate() { + // Ensure no element with focus can leak, if we don't do this the root VM is retained by Window.EffectiveValues + // https://stackoverflow.com/a/30864434 + Keyboard.ClearFocus(); + + MainMessageQueue = null; _titleUpdateTimer.Stop(); var window = (MaterialWindow) View; _windowSize.Value ??= new WindowSize(); _windowSize.Value.ApplyFromWindow(window); _windowSize.Save(); + + _titleUpdateTimer.Elapsed -= OnTitleUpdateTimerOnElapsed; + _colorScheme.SettingChanged -= ColorSchemeOnSettingChanged; + _themeWatcher.ThemeChanged -= ThemeWatcherOnThemeChanged; + SidebarViewModel.PropertyChanged -= SidebarViewModelOnPropertyChanged; + } + + protected override void OnClose() + { + SidebarViewModel.Dispose(); + + // Lets force the GC to run after closing the window so it is obvious to users watching task manager + // that closing the UI will decrease the memory footprint of the application. + Task.Run(async () => + { + await Task.Delay(TimeSpan.FromSeconds(15)); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + }); } #endregion diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs index 66a7d317a..f7a0e6ef4 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Timers; using Artemis.Core.Events; using Artemis.Core.Services.Interfaces; -using Artemis.UI.Services; using Artemis.UI.Shared.DataModelVisualization.Shared; using Artemis.UI.Shared.Services; using Stylet; @@ -16,17 +15,16 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs private readonly IPluginService _pluginService; private readonly Timer _updateTimer; private bool _isModuleFilterEnabled; - private Core.Plugins.Abstract.Module _selectedModule; private DataModelPropertiesViewModel _mainDataModel; - private string _propertySearch; private List _modules; + private string _propertySearch; + private Core.Plugins.Abstract.Module _selectedModule; public DataModelDebugViewModel(IDataModelVisualizationService dataModelVisualizationService, IPluginService pluginService) { _dataModelVisualizationService = dataModelVisualizationService; _pluginService = pluginService; _updateTimer = new Timer(500); - _updateTimer.Elapsed += (sender, args) => MainDataModel.Update(_dataModelVisualizationService); DisplayName = "Data model"; } @@ -77,6 +75,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs { GetDataModel(); _updateTimer.Start(); + _updateTimer.Elapsed += OnUpdateTimerOnElapsed; _pluginService.PluginEnabled += PluginServiceOnPluginToggled; _pluginService.PluginDisabled += PluginServiceOnPluginToggled; @@ -86,10 +85,16 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs protected override void OnDeactivate() { _updateTimer.Stop(); + _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; _pluginService.PluginEnabled -= PluginServiceOnPluginToggled; _pluginService.PluginDisabled -= PluginServiceOnPluginToggled; } + private void OnUpdateTimerOnElapsed(object sender, ElapsedEventArgs args) + { + MainDataModel.Update(_dataModelVisualizationService); + } + private void GetDataModel() { MainDataModel = SelectedModule != null diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs index 0bb7decdf..4eb80341c 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs +++ b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs @@ -19,7 +19,7 @@ using Stylet; namespace Artemis.UI.Screens.Sidebar { - public class SidebarViewModel : PropertyChangedBase, IHandle + public class SidebarViewModel : PropertyChangedBase, IHandle, IDisposable { private readonly IKernel _kernel; private readonly IModuleVmFactory _moduleVmFactory; @@ -37,10 +37,10 @@ namespace Artemis.UI.Screens.Sidebar SidebarModules = new Dictionary(); SidebarItems = new BindableCollection(); - SetupSidebar(); _pluginService.PluginEnabled += PluginServiceOnPluginEnabled; _pluginService.PluginDisabled += PluginServiceOnPluginDisabled; + SetupSidebar(); eventAggregator.Subscribe(this); } @@ -64,7 +64,7 @@ namespace Artemis.UI.Screens.Sidebar public void SetupSidebar() { - SidebarItems.Clear(); + SidebarItems.Clear(); SidebarModules.Clear(); // Add all default sidebar items @@ -194,5 +194,11 @@ namespace Artemis.UI.Screens.Sidebar } #endregion + + public void Dispose() + { + _pluginService.PluginEnabled -= PluginServiceOnPluginEnabled; + _pluginService.PluginDisabled -= PluginServiceOnPluginDisabled; + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Splash/SplashViewModel.cs b/src/Artemis.UI/Screens/Splash/SplashViewModel.cs index 2ce1dd927..d1ea31ff0 100644 --- a/src/Artemis.UI/Screens/Splash/SplashViewModel.cs +++ b/src/Artemis.UI/Screens/Splash/SplashViewModel.cs @@ -51,6 +51,8 @@ namespace Artemis.UI.Screens.Splash _pluginService.CopyingBuildInPlugins -= OnPluginServiceOnCopyingBuildInPlugins; _pluginService.PluginLoading -= OnPluginServiceOnPluginLoading; _pluginService.PluginLoaded -= OnPluginServiceOnPluginLoaded; + _pluginService.PluginEnabling -= PluginServiceOnPluginEnabling; + _pluginService.PluginEnabled -= PluginServiceOnPluginEnabled; base.OnClose(); } diff --git a/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigView.xaml b/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigView.xaml index 66dd4d947..7bfa6ebfe 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigView.xaml +++ b/src/Artemis.UI/Screens/SurfaceEditor/Dialogs/SurfaceDeviceConfigView.xaml @@ -12,13 +12,23 @@ - - + + + + - - -