From 24afb6b0f52180a23490419cf73b6ef2b22e0bc4 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Fri, 19 Jun 2020 21:48:26 +0200 Subject: [PATCH] UI - Large amount of memory improvements in the VMs Core - Minor memory improvements --- .../Extensions/RgbDeviceExtensions.cs | 19 +++-- src/Artemis.Core/Models/Profile/Folder.cs | 6 +- src/Artemis.Core/Models/Profile/Layer.cs | 17 ++-- .../Models/Profile/LayerShapes/LayerShape.cs | 10 +-- .../Profile/PropertiesProfileElement.cs | 4 +- src/Artemis.Core/RGB.NET/BitmapBrush.cs | 2 +- src/Artemis.Core/RGB.NET/GraphicsDecorator.cs | 56 ------------- .../Behaviors/InputBindingBehavior.cs | 28 ++++++- .../LayerPropertiesViewModel.cs | 7 ++ .../Timeline/TimelineKeyframeViewModel.cs | 67 +--------------- .../TimelinePropertyGroupViewModel.cs | 3 +- .../Timeline/TimelinePropertyView.xaml | 2 +- .../Timeline/TimelinePropertyViewModel.cs | 9 +-- .../Timeline/TimelineViewModel.cs | 79 +++++++++++++++++-- .../Tree/TreePropertyGroupViewModel.cs | 3 +- .../Tree/TreePropertyViewModel.cs | 3 +- .../ProfileTree/ProfileTreeViewModel.cs | 7 ++ .../ProfileTree/TreeItem/FolderView.xaml | 1 + .../ProfileTree/TreeItem/FolderViewModel.cs | 12 +++ .../ProfileTree/TreeItem/LayerView.xaml | 1 + .../ProfileTree/TreeItem/TreeItemViewModel.cs | 30 ++++--- .../Visualization/CanvasViewModel.cs | 18 ++++- .../Visualization/ProfileLayerViewModel.cs | 17 ++-- .../Visualization/ProfileViewModel.cs | 7 +- .../Visualization/Tools/EditToolViewModel.cs | 2 +- .../ColorBrush.cs | 24 +++++- 26 files changed, 243 insertions(+), 191 deletions(-) delete mode 100644 src/Artemis.Core/RGB.NET/GraphicsDecorator.cs diff --git a/src/Artemis.Core/Extensions/RgbDeviceExtensions.cs b/src/Artemis.Core/Extensions/RgbDeviceExtensions.cs index e4a637e37..16ffad995 100644 --- a/src/Artemis.Core/Extensions/RgbDeviceExtensions.cs +++ b/src/Artemis.Core/Extensions/RgbDeviceExtensions.cs @@ -1,4 +1,5 @@ -using RGB.NET.Core; +using System.Text; +using RGB.NET.Core; namespace Artemis.Core.Extensions { @@ -6,11 +7,17 @@ namespace Artemis.Core.Extensions { public static string GetDeviceIdentifier(this IRGBDevice rgbDevice) { - return rgbDevice.DeviceInfo.DeviceName + - "-" + rgbDevice.DeviceInfo.Manufacturer + - "-" + rgbDevice.DeviceInfo.Model + - "-" + rgbDevice.DeviceInfo.DeviceType + - "-" + rgbDevice.DeviceInfo.Lighting; + var builder = new StringBuilder(); + builder.Append(rgbDevice.DeviceInfo.DeviceName); + builder.Append('-'); + builder.Append(rgbDevice.DeviceInfo.Manufacturer); + builder.Append('-'); + builder.Append(rgbDevice.DeviceInfo.Model); + builder.Append('-'); + builder.Append(rgbDevice.DeviceInfo.DeviceType); + builder.Append('-'); + builder.Append(rgbDevice.DeviceInfo.Lighting); + return builder.ToString(); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index 0a3ff7fb8..1ccb64cbc 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -80,7 +80,7 @@ namespace Artemis.Core.Models.Profile { if (!Enabled || Path == null || !Children.Any(c => c.Enabled)) return; - + if (_folderBitmap == null) _folderBitmap = new SKBitmap(new SKImageInfo((int) Path.Bounds.Width, (int) Path.Bounds.Height)); else if (_folderBitmap.Info.Width != (int) Path.Bounds.Width || _folderBitmap.Info.Height != (int) Path.Bounds.Height) @@ -89,11 +89,11 @@ namespace Artemis.Core.Models.Profile _folderBitmap = new SKBitmap(new SKImageInfo((int) Path.Bounds.Width, (int) Path.Bounds.Height)); } + using var folderPath = new SKPath(Path); using var folderCanvas = new SKCanvas(_folderBitmap); using var folderPaint = new SKPaint(); folderCanvas.Clear(); - var folderPath = new SKPath(Path); folderPath.Transform(SKMatrix.MakeTranslation(folderPath.Bounds.Left * -1, folderPath.Bounds.Top * -1)); foreach (var baseLayerEffect in LayerEffects.Where(e => e.Enabled)) @@ -112,7 +112,7 @@ namespace Artemis.Core.Models.Profile canvas.Save(); - var clipPath = new SKPath(folderPath); + using var clipPath = new SKPath(folderPath); clipPath.Transform(SKMatrix.MakeTranslation(targetLocation.X, targetLocation.Y)); canvas.ClipPath(clipPath); diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index f05e0130f..ca73d9123 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -246,6 +246,7 @@ namespace Artemis.Core.Models.Profile _layerBitmap = new SKBitmap(new SKImageInfo((int)Path.Bounds.Width, (int)Path.Bounds.Height)); } + using var layerPath = new SKPath(Path); using var layerCanvas = new SKCanvas(_layerBitmap); using var layerPaint = new SKPaint { @@ -254,8 +255,7 @@ namespace Artemis.Core.Models.Profile Color = new SKColor(0, 0, 0, (byte) (Transform.Opacity.CurrentValue * 2.55f)) }; layerCanvas.Clear(); - - var layerPath = new SKPath(Path); + layerPath.Transform(SKMatrix.MakeTranslation(layerPath.Bounds.Left * -1, layerPath.Bounds.Top * -1)); foreach (var baseLayerEffect in LayerEffects.Where(e => e.Enabled)) @@ -278,11 +278,13 @@ namespace Artemis.Core.Models.Profile targetLocation = Path.Bounds.Location - parentFolder.Path.Bounds.Location; canvas.DrawBitmap(_layerBitmap, targetLocation, layerPaint); + } private void SimpleRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint, SKPath layerPath) { - LayerBrush.InternalRender(canvas, canvasInfo, new SKPath(LayerShape.Path), paint); + using var renderPath = new SKPath(LayerShape.Path); + LayerBrush.InternalRender(canvas, canvasInfo, renderPath, paint); } private void StretchRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint, SKPath layerPath) @@ -302,8 +304,9 @@ namespace Artemis.Core.Models.Profile canvas.RotateDegrees(rotationProperty, anchorPosition.X, anchorPosition.Y); canvas.Scale(sizeProperty.Width / 100f, sizeProperty.Height / 100f, anchorPosition.X, anchorPosition.Y); canvas.Translate(x, y); - - LayerBrush.InternalRender(canvas, canvasInfo, new SKPath(LayerShape.Path), paint); + + using var renderPath = new SKPath(LayerShape.Path); + LayerBrush.InternalRender(canvas, canvasInfo, renderPath, paint); } private void ClipRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint, SKPath layerPath) @@ -319,7 +322,7 @@ namespace Artemis.Core.Models.Profile var x = anchorPosition.X - layerPath.Bounds.MidX - anchorProperty.X * layerPath.Bounds.Width; var y = anchorPosition.Y - layerPath.Bounds.MidY - anchorProperty.Y * layerPath.Bounds.Height; - var clipPath = new SKPath(LayerShape.Path); + using var clipPath = new SKPath(LayerShape.Path); clipPath.Transform(SKMatrix.MakeTranslation(x, y)); clipPath.Transform(SKMatrix.MakeScale(sizeProperty.Width / 100f, sizeProperty.Height / 100f, anchorPosition.X, anchorPosition.Y)); clipPath.Transform(SKMatrix.MakeRotationDegrees(rotationProperty, anchorPosition.X, anchorPosition.Y)); @@ -336,7 +339,7 @@ namespace Artemis.Core.Models.Profile Math.Max(clipPath.Bounds.Right - x, Bounds.Right - x), Math.Max(clipPath.Bounds.Bottom - y, Bounds.Bottom - y) ); - var renderPath = new SKPath(); + using var renderPath = new SKPath(); renderPath.AddRect(boundsRect); LayerBrush.InternalRender(canvas, canvasInfo, renderPath, paint); diff --git a/src/Artemis.Core/Models/Profile/LayerShapes/LayerShape.cs b/src/Artemis.Core/Models/Profile/LayerShapes/LayerShape.cs index e7c878307..61b15eb95 100644 --- a/src/Artemis.Core/Models/Profile/LayerShapes/LayerShape.cs +++ b/src/Artemis.Core/Models/Profile/LayerShapes/LayerShape.cs @@ -4,8 +4,6 @@ namespace Artemis.Core.Models.Profile.LayerShapes { public abstract class LayerShape { - private SKPath _path; - protected LayerShape(Layer layer) { Layer = layer; @@ -17,13 +15,9 @@ namespace Artemis.Core.Models.Profile.LayerShapes public Layer Layer { get; set; } /// - /// Gets a copy of the path outlining the shape + /// Gets a the path outlining the shape /// - public SKPath Path - { - get => _path != null ? new SKPath(_path) : null; - protected set => _path = value; - } + public SKPath Path { get; protected set; } public abstract void CalculateRenderProperties(); } diff --git a/src/Artemis.Core/Models/Profile/PropertiesProfileElement.cs b/src/Artemis.Core/Models/Profile/PropertiesProfileElement.cs index fbbaa5b7b..ae1371424 100644 --- a/src/Artemis.Core/Models/Profile/PropertiesProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/PropertiesProfileElement.cs @@ -11,12 +11,12 @@ namespace Artemis.Core.Models.Profile private SKPath _path; /// - /// Gets a copy of the path containing all the LEDs this entity is applied to, any rendering outside the entity Path is + /// Gets the path containing all the LEDs this entity is applied to, any rendering outside the entity Path is /// clipped. /// public SKPath Path { - get => _path != null ? new SKPath(_path) : null; + get => _path; protected set { _path = value; diff --git a/src/Artemis.Core/RGB.NET/BitmapBrush.cs b/src/Artemis.Core/RGB.NET/BitmapBrush.cs index fc1e82f96..eaf01451a 100644 --- a/src/Artemis.Core/RGB.NET/BitmapBrush.cs +++ b/src/Artemis.Core/RGB.NET/BitmapBrush.cs @@ -98,7 +98,7 @@ namespace Artemis.Core.RGB.NET var bitmapWidth = Bitmap.Width; var bitmapHeight = Bitmap.Height; - var pixmap = Bitmap.PeekPixels(); + using var pixmap = Bitmap.PeekPixels(); foreach (var renderTarget in renderTargets) { // SKRect has all the good stuff we need diff --git a/src/Artemis.Core/RGB.NET/GraphicsDecorator.cs b/src/Artemis.Core/RGB.NET/GraphicsDecorator.cs deleted file mode 100644 index 3cf61959b..000000000 --- a/src/Artemis.Core/RGB.NET/GraphicsDecorator.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Linq; -using Artemis.Core.Extensions; -using RGB.NET.Core; -using SkiaSharp; - -namespace Artemis.Core.RGB.NET -{ - public class GraphicsDecorator : AbstractDecorator, IBrushDecorator, IDisposable - { - private readonly double _scale; - - public GraphicsDecorator(ILedGroup ledGroup, double scale) - { - _scale = scale; - - var leds = ledGroup.GetLeds().ToList(); - if (!leds.Any()) - return; - - var width = Math.Min(leds.Max(l => l.AbsoluteLedRectangle.Location.X + l.AbsoluteLedRectangle.Size.Width) * scale, 4096); - var height = Math.Min(leds.Max(l => l.AbsoluteLedRectangle.Location.Y + l.AbsoluteLedRectangle.Size.Height) * scale, 4096); - Bitmap = new SKBitmap(new SKImageInfo(width.RoundToInt(), height.RoundToInt())); - } - - public SKBitmap Bitmap { get; private set; } - - public Color ManipulateColor(Rectangle rectangle, BrushRenderTarget renderTarget, Color color) - { - if (Bitmap == null) - return new Color(0, 0, 0); - - var x = renderTarget.Led.AbsoluteLedRectangle.Center.X * _scale; - var y = renderTarget.Led.AbsoluteLedRectangle.Center.Y * _scale; - - var pixel = Bitmap.GetPixel(RoundToInt(x), RoundToInt(y)); - return new Color(pixel.Alpha, pixel.Red, pixel.Green, pixel.Blue); - } - - public override void OnDetached(IDecoratable decoratable) - { - Dispose(); - } - - public void Dispose() - { - Bitmap?.Dispose(); - Bitmap = null; - } - - private int RoundToInt(double number) - { - return (int) Math.Round(number, MidpointRounding.AwayFromZero); - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Behaviors/InputBindingBehavior.cs b/src/Artemis.UI/Behaviors/InputBindingBehavior.cs index bd9c7f996..81897d101 100644 --- a/src/Artemis.UI/Behaviors/InputBindingBehavior.cs +++ b/src/Artemis.UI/Behaviors/InputBindingBehavior.cs @@ -1,9 +1,15 @@ -using System.Windows; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Input; namespace Artemis.UI.Behaviors { public class InputBindingBehavior { + private static List> _movedInputBindings = new List>(); + public static readonly DependencyProperty PropagateInputBindingsToWindowProperty = DependencyProperty.RegisterAttached("PropagateInputBindingsToWindow", typeof(bool), typeof(InputBindingBehavior), new PropertyMetadata(false, OnPropagateInputBindingsToWindowChanged)); @@ -21,13 +27,13 @@ namespace Artemis.UI.Behaviors private static void OnPropagateInputBindingsToWindowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((FrameworkElement) d).Loaded += OnLoaded; + ((FrameworkElement) d).Unloaded += OnUnloaded; } private static void OnLoaded(object sender, RoutedEventArgs e) { var frameworkElement = (FrameworkElement) sender; - frameworkElement.Loaded -= OnLoaded; - + var window = Window.GetWindow(frameworkElement); if (window == null) return; @@ -37,7 +43,23 @@ namespace Artemis.UI.Behaviors var inputBinding = frameworkElement.InputBindings[i]; window.InputBindings.Add(inputBinding); frameworkElement.InputBindings.Remove(inputBinding); + + _movedInputBindings.Add(new Tuple(frameworkElement, window, inputBinding)); } } + + private static void OnUnloaded(object sender, RoutedEventArgs e) + { + var frameworkElement = (FrameworkElement) sender; + + var toRemove = _movedInputBindings.Where(m => m.Item1 == frameworkElement).ToList(); + foreach (var (_, window, inputBinding) in toRemove) + { + if (window.InputBindings.Contains(inputBinding)) + window.InputBindings.Remove(inputBinding); + } + + _movedInputBindings = _movedInputBindings.Where(b => b.Item1 != frameworkElement).ToList(); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index d0ccdaa58..306f53bad 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -86,9 +86,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties ProfileEditorService.CurrentTimeChanged -= ProfileEditorServiceOnCurrentTimeChanged; ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged; + PopulateProperties(null); base.OnClose(); } + protected override void OnActivate() + { + PopulateProperties(ProfileEditorService.SelectedProfileElement as PropertiesProfileElement); + base.OnActivate(); + } + protected override void OnDeactivate() { Pause(); 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 043427405..0b821cefb 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineKeyframeViewModel.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using System.Windows; -using System.Windows.Input; using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Utilities; using Artemis.UI.Shared.Services.Interfaces; @@ -13,8 +12,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline { private readonly IProfileEditorService _profileEditorService; - public TimelineKeyframeViewModel(IProfileEditorService profileEditorService, TimelineViewModel timelineViewModel, LayerPropertyKeyframe layerPropertyKeyframe) - : base(profileEditorService, timelineViewModel, layerPropertyKeyframe) + public TimelineKeyframeViewModel(IProfileEditorService profileEditorService, LayerPropertyKeyframe layerPropertyKeyframe) + : base(profileEditorService, layerPropertyKeyframe) { _profileEditorService = profileEditorService; LayerPropertyKeyframe = layerPropertyKeyframe; @@ -48,13 +47,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline public abstract class TimelineKeyframeViewModel : PropertyChangedBase { private readonly IProfileEditorService _profileEditorService; - private readonly TimelineViewModel _timelineViewModel; private int _pixelsPerSecond; - protected TimelineKeyframeViewModel(IProfileEditorService profileEditorService, TimelineViewModel timelineViewModel, BaseLayerPropertyKeyframe baseLayerPropertyKeyframe) + protected TimelineKeyframeViewModel(IProfileEditorService profileEditorService, BaseLayerPropertyKeyframe baseLayerPropertyKeyframe) { _profileEditorService = profileEditorService; - _timelineViewModel = timelineViewModel; BaseLayerPropertyKeyframe = baseLayerPropertyKeyframe; EasingViewModels = new BindableCollection(); } @@ -78,64 +75,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline #region Keyframe movement - public void KeyframeMouseDown(object sender, MouseButtonEventArgs e) - { - if (e.LeftButton == MouseButtonState.Released) - return; - - ((IInputElement) sender).CaptureMouse(); - if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) && !IsSelected) - _timelineViewModel.SelectKeyframe(this, true, false); - else if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) - _timelineViewModel.SelectKeyframe(this, false, true); - else if (!IsSelected) - _timelineViewModel.SelectKeyframe(this, false, false); - - e.Handled = true; - } - - public void KeyframeMouseUp(object sender, MouseButtonEventArgs e) - { - _profileEditorService.UpdateSelectedProfileElement(); - _timelineViewModel.ReleaseSelectedKeyframes(); - - ((IInputElement) sender).ReleaseMouseCapture(); - } - - public void KeyframeMouseMove(object sender, MouseEventArgs e) - { - if (e.LeftButton == MouseButtonState.Pressed) - _timelineViewModel.MoveSelectedKeyframes(GetCursorTime(e.GetPosition(ParentView))); - - e.Handled = true; - } - - private TimeSpan GetCursorTime(Point position) - { - // Get the parent grid, need that for our position - var x = Math.Max(0, position.X); - var time = TimeSpan.FromSeconds(x / _pixelsPerSecond); - - // Round the time to something that fits the current zoom level - if (_pixelsPerSecond < 200) - time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds / 5.0) * 5.0); - else if (_pixelsPerSecond < 500) - time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds / 2.0) * 2.0); - else - time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds)); - - // If shift is held, snap to the current time - // Take a tolerance of 5 pixels (half a keyframe width) - if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) - { - var tolerance = 1000f / _pixelsPerSecond * 5; - if (Math.Abs(_profileEditorService.CurrentTime.TotalMilliseconds - time.TotalMilliseconds) < tolerance) - time = _profileEditorService.CurrentTime; - } - - return time; - } - #endregion #region Easing diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupViewModel.cs index 69e8c5bc9..991f5a9e3 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyGroupViewModel.cs @@ -6,7 +6,7 @@ using Stylet; namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline { - public class TimelinePropertyGroupViewModel + public class TimelinePropertyGroupViewModel : PropertyChangedBase { public TimelinePropertyGroupViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) { @@ -20,7 +20,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline public LayerPropertyGroupViewModel LayerPropertyGroupViewModel { get; } public BindableCollection TimelineKeyframeViewModels { get; set; } - public TimelineViewModel TimelineViewModel { get; set; } public void UpdateKeyframes() { diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml index e4e8d112e..350b4c634 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyView.xaml @@ -33,7 +33,7 @@ Height="10" Margin="-5,6,0,0" ToolTip="{Binding Timestamp}" - s:View.ActionTarget="{Binding}" + s:View.ActionTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TimelineView}}, Path=DataContext}" MouseDown="{s:Action KeyframeMouseDown}" MouseUp="{s:Action KeyframeMouseUp}" MouseMove="{s:Action KeyframeMouseMove}" diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs index a36a04d1c..afe080138 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelinePropertyViewModel.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using Artemis.UI.Exceptions; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; using Artemis.UI.Shared.Services.Interfaces; using Stylet; @@ -26,9 +25,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline public override void UpdateKeyframes() { - if (TimelineViewModel == null) - throw new ArtemisUIException("Timeline view model must be set before keyframes can be updated"); - // Only show keyframes if they are enabled if (LayerPropertyViewModel.LayerProperty.KeyframesEnabled) { @@ -37,7 +33,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline TimelineKeyframeViewModels.RemoveRange(toRemove); TimelineKeyframeViewModels.AddRange( keyframes.Where(k => TimelineKeyframeViewModels.All(t => t.BaseLayerPropertyKeyframe != k)) - .Select(k => new TimelineKeyframeViewModel(_profileEditorService, TimelineViewModel, k)) + .Select(k => new TimelineKeyframeViewModel(_profileEditorService, k)) ); } else @@ -67,7 +63,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline } } - public abstract class TimelinePropertyViewModel : IDisposable + public abstract class TimelinePropertyViewModel : PropertyChangedBase, IDisposable { protected TimelinePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) { @@ -76,7 +72,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline } public LayerPropertyBaseViewModel LayerPropertyBaseViewModel { get; } - public TimelineViewModel TimelineViewModel { get; set; } public BindableCollection TimelineKeyframeViewModels { get; set; } public abstract void Dispose(); 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 91288fed7..959a10457 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/TimelineViewModel.cs @@ -4,19 +4,24 @@ using System.Linq; using System.Windows; using System.Windows.Input; using System.Windows.Media; +using System.Windows.Shapes; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; +using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Utilities; using Stylet; namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline { - public class TimelineViewModel : PropertyChangedBase + public class TimelineViewModel : Screen { private readonly LayerPropertiesViewModel _layerPropertiesViewModel; + private readonly IProfileEditorService _profileEditorService; - public TimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection layerPropertyGroups) + public TimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, BindableCollection layerPropertyGroups, + IProfileEditorService profileEditorService) { _layerPropertiesViewModel = layerPropertiesViewModel; + _profileEditorService = profileEditorService; LayerPropertyGroups = layerPropertyGroups; SelectionRectangle = new RectangleGeometry(); @@ -32,20 +37,82 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline { foreach (var layerPropertyGroupViewModel in LayerPropertyGroups) { - layerPropertyGroupViewModel.TimelinePropertyGroupViewModel.TimelineViewModel = this; layerPropertyGroupViewModel.TimelinePropertyGroupViewModel.UpdateKeyframes(); foreach (var layerPropertyBaseViewModel in layerPropertyGroupViewModel.GetAllChildren()) { if (layerPropertyBaseViewModel is LayerPropertyViewModel layerPropertyViewModel) - { - layerPropertyViewModel.TimelinePropertyBaseViewModel.TimelineViewModel = this; layerPropertyViewModel.TimelinePropertyBaseViewModel.UpdateKeyframes(); - } } } } + #region Command handlers + + public void KeyframeMouseDown(object sender, MouseButtonEventArgs e) + { + if (e.LeftButton == MouseButtonState.Released) + return; + + var viewModel = (sender as Ellipse)?.DataContext as TimelineKeyframeViewModel; + if (viewModel == null) + return; + + ((IInputElement) sender).CaptureMouse(); + if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) && !viewModel.IsSelected) + SelectKeyframe(viewModel, true, false); + else if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) + SelectKeyframe(viewModel, false, true); + else if (!viewModel.IsSelected) + SelectKeyframe(viewModel, false, false); + + e.Handled = true; + } + + public void KeyframeMouseUp(object sender, MouseButtonEventArgs e) + { + _profileEditorService.UpdateSelectedProfileElement(); + ReleaseSelectedKeyframes(); + + ((IInputElement) sender).ReleaseMouseCapture(); + } + + public void KeyframeMouseMove(object sender, MouseEventArgs e) + { + if (e.LeftButton == MouseButtonState.Pressed) + MoveSelectedKeyframes(GetCursorTime(e.GetPosition(View))); + + e.Handled = true; + } + + private TimeSpan GetCursorTime(Point position) + { + // Get the parent grid, need that for our position + var x = Math.Max(0, position.X); + var time = TimeSpan.FromSeconds(x / _profileEditorService.PixelsPerSecond); + + // Round the time to something that fits the current zoom level + if (_profileEditorService.PixelsPerSecond < 200) + time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds / 5.0) * 5.0); + else if (_profileEditorService.PixelsPerSecond < 500) + time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds / 2.0) * 2.0); + else + time = TimeSpan.FromMilliseconds(Math.Round(time.TotalMilliseconds)); + + // If shift is held, snap to the current time + // Take a tolerance of 5 pixels (half a keyframe width) + if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) + { + var tolerance = 1000f / _profileEditorService.PixelsPerSecond * 5; + if (Math.Abs(_profileEditorService.CurrentTime.TotalMilliseconds - time.TotalMilliseconds) < tolerance) + time = _profileEditorService.CurrentTime; + } + + return time; + } + + #endregion + #region Keyframe movement public void MoveSelectedKeyframes(TimeSpan cursorTime) diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyGroupViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyGroupViewModel.cs index e47b45076..8aba6b8f6 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyGroupViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyGroupViewModel.cs @@ -4,10 +4,11 @@ using Artemis.Core.Services.Interfaces; using Artemis.UI.Screens.Module.ProfileEditor.Dialogs; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; using Artemis.UI.Shared.Services.Interfaces; +using Stylet; namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree { - public class TreePropertyGroupViewModel + public class TreePropertyGroupViewModel : PropertyChangedBase { private readonly IDialogService _dialogService; private readonly ILayerService _layerService; diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs index 4162a9a51..b786a992a 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Tree/TreePropertyViewModel.cs @@ -5,6 +5,7 @@ using Artemis.Core.Utilities; using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Abstract; using Artemis.UI.Shared.PropertyInput; using Artemis.UI.Shared.Services.Interfaces; +using Stylet; namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree { @@ -56,7 +57,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Tree } } - public abstract class TreePropertyViewModel : IDisposable + public abstract class TreePropertyViewModel : PropertyChangedBase, IDisposable { protected TreePropertyViewModel(LayerPropertyBaseViewModel layerPropertyBaseViewModel) { diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs index 272530ad6..e2a4c1145 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs @@ -135,6 +135,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree } } + protected override void OnClose() + { + RootFolder?.Dispose(); + RootFolder = null; + base.OnClose(); + } + #region Event handlers private void OnProfileElementSelected(object sender, EventArgs e) diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/FolderView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/FolderView.xaml index f094176b4..35ecdc2ef 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/FolderView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/FolderView.xaml @@ -55,6 +55,7 @@ Width="18" Height="18" IsChecked="{Binding ProfileElement.Enabled}" + Command="{s:Action EnableToggled}" VerticalAlignment="Center" Padding="-25"> diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/FolderViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/FolderViewModel.cs index cf4722c41..4dd168414 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/FolderViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/FolderViewModel.cs @@ -7,6 +7,18 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem { public class FolderViewModel : TreeItemViewModel { + protected override void Dispose(bool disposing) + { + if (disposing) + { + foreach (var treeItemViewModel in Children) + treeItemViewModel.Dispose(); + Children.Clear(); + } + + base.Dispose(disposing); + } + // I hate this about DI, oh well public FolderViewModel(ProfileElement folder, IProfileEditorService profileEditorService, diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/LayerView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/LayerView.xaml index b0ade2a89..1cb6d3db4 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/LayerView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/LayerView.xaml @@ -48,6 +48,7 @@ Width="18" Height="18" IsChecked="{Binding ProfileElement.Enabled}" + Command="{s:Action EnableToggled}" VerticalAlignment="Center" Padding="-25"> 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 863a7e2da..c79e31070 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Threading.Tasks; @@ -12,7 +13,7 @@ using Stylet; namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem { - public abstract class TreeItemViewModel : PropertyChangedBase + public abstract class TreeItemViewModel : PropertyChangedBase, IDisposable { private readonly IDialogService _dialogService; private readonly IFolderVmFactory _folderVmFactory; @@ -38,16 +39,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem ProfileElement = profileElement; Children = new BindableCollection(); - ProfileElement.PropertyChanged += HandleProfileElementEnabledChanged; UpdateProfileElements(); } - private void HandleProfileElementEnabledChanged(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(ProfileElement.Enabled)) - _profileEditorService.UpdateSelectedProfile(); - } - public TreeItemViewModel Parent { get; set; } public ProfileElement ProfileElement { get; set; } @@ -208,5 +202,23 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem foreach (var treeItemViewModel in newChildren) treeItemViewModel.UpdateProfileElements(); } + + public void EnableToggled() + { + _profileEditorService.UpdateSelectedProfile(); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/CanvasViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/CanvasViewModel.cs index 6cdf3689b..85a4686f6 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/CanvasViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/CanvasViewModel.cs @@ -1,10 +1,24 @@ -using Stylet; +using System; +using Stylet; namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization { - public class CanvasViewModel : PropertyChangedBase + public abstract class CanvasViewModel : PropertyChangedBase, IDisposable { public double X { get; set; } public double Y { get; set; } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs index 652cf7a62..0176879ac 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs @@ -44,14 +44,19 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization public Thickness LayerPosition => new Thickness(ViewportRectangle.Left, ViewportRectangle.Top, 0, 0); public bool IsSelected { get; set; } - public void Dispose() + protected override void Dispose(bool disposing) { - Layer.RenderPropertiesUpdated -= LayerOnRenderPropertiesUpdated; - _profileEditorService.ProfileElementSelected -= OnProfileElementSelected; - _profileEditorService.SelectedProfileElementUpdated -= OnSelectedProfileElementUpdated; - _profileEditorService.ProfilePreviewUpdated -= ProfileEditorServiceOnProfilePreviewUpdated; - } + if (disposing) + { + Layer.RenderPropertiesUpdated -= LayerOnRenderPropertiesUpdated; + _profileEditorService.ProfileElementSelected -= OnProfileElementSelected; + _profileEditorService.SelectedProfileElementUpdated -= OnSelectedProfileElementUpdated; + _profileEditorService.ProfilePreviewUpdated -= ProfileEditorServiceOnProfilePreviewUpdated; + } + base.Dispose(disposing); + } + private void Update() { IsSelected = _profileEditorService.SelectedProfileElement == Layer; diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs index b3112afbc..64fc12dd7 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs @@ -60,7 +60,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization }); ApplySurfaceConfiguration(_surfaceService.ActiveSurface); - ApplyActiveProfile(); ActivateToolByIndex(0); eventAggregator.Subscribe(this); @@ -151,10 +150,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization _profileEditorService.ProfileSelected -= OnProfileSelected; _profileEditorService.ProfileElementSelected -= OnProfileElementSelected; _profileEditorService.SelectedProfileElementUpdated -= OnSelectedProfileElementUpdated; + if (_previousSelectedLayer != null) + _previousSelectedLayer.LayerBrushUpdated -= SelectedLayerOnLayerBrushUpdated; OnlyShowSelectedShape.Save(); HighlightSelectedLayer.Save(); + foreach (var canvasViewModel in CanvasViewModels) + canvasViewModel.Dispose(); + CanvasViewModels.Clear(); + base.OnClose(); } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolViewModel.cs index 0d905f5c2..9da3a8a34 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolViewModel.cs @@ -361,7 +361,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools private SKPoint[] UnTransformPoints(SKPoint[] skPoints, Layer layer, SKPoint pivot, bool includeScale) { - var counterRotatePath = new SKPath(); + using var counterRotatePath = new SKPath(); counterRotatePath.AddPoly(skPoints, false); counterRotatePath.Transform(SKMatrix.MakeRotationDegrees(layer.Transform.Rotation.CurrentValue * -1, pivot.X, pivot.Y)); if (includeScale) diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs index f6e6e9b14..43338f7cd 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using Artemis.Core.Plugins.LayerBrush.Abstract; using SkiaSharp; @@ -13,14 +14,19 @@ namespace Artemis.Plugins.LayerBrushes.Color public override void EnableLayerBrush() { - Layer.RenderPropertiesUpdated += (sender, args) => CreateShader(); - Properties.GradientType.BaseValueChanged += (sender, args) => CreateShader(); - Properties.Color.BaseValueChanged += (sender, args) => CreateShader(); - Properties.Gradient.BaseValue.PropertyChanged += (sender, args) => CreateShader(); + Layer.RenderPropertiesUpdated += HandleShaderChange; + Properties.GradientType.BaseValueChanged += HandleShaderChange; + Properties.Color.BaseValueChanged += HandleShaderChange; + Properties.Gradient.BaseValue.PropertyChanged += BaseValueOnPropertyChanged; } public override void DisableLayerBrush() { + Layer.RenderPropertiesUpdated -= HandleShaderChange; + Properties.GradientType.BaseValueChanged -= HandleShaderChange; + Properties.Color.BaseValueChanged -= HandleShaderChange; + Properties.Gradient.BaseValue.PropertyChanged -= BaseValueOnPropertyChanged; + _paint?.Dispose(); _shader?.Dispose(); _paint = null; @@ -50,6 +56,16 @@ namespace Artemis.Plugins.LayerBrushes.Color canvas.DrawPath(path, paint); } + private void BaseValueOnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + CreateShader(); + } + + private void HandleShaderChange(object? sender, EventArgs e) + { + CreateShader(); + } + private void CreateShader() { var center = new SKPoint(_shaderBounds.MidX, _shaderBounds.MidY);