diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs
index 5c9ea5812..427ad1808 100644
--- a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs
+++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
using System.Linq;
@@ -9,15 +11,15 @@ using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
+using System.Windows.Threading;
using Artemis.Core;
-using Stylet;
namespace Artemis.UI.Shared
{
///
/// Visualizes an with optional per-LED colors
///
- public class DeviceVisualizer : FrameworkElement, IDisposable
+ public class DeviceVisualizer : FrameworkElement
{
///
/// The device to visualize
@@ -34,14 +36,16 @@ namespace Artemis.UI.Shared
///
/// A list of LEDs to highlight
///
- public static readonly DependencyProperty HighlightedLedsProperty = DependencyProperty.Register(nameof(HighlightedLeds), typeof(IEnumerable), typeof(DeviceVisualizer),
- new FrameworkPropertyMetadata(default(IEnumerable)));
+ public static readonly DependencyProperty HighlightedLedsProperty = DependencyProperty.Register(nameof(HighlightedLeds), typeof(ObservableCollection), typeof(DeviceVisualizer),
+ new FrameworkPropertyMetadata(default(ObservableCollection), HighlightedLedsPropertyChanged));
private readonly DrawingGroup _backingStore;
private readonly List _deviceVisualizerLeds;
- private readonly Timer _timer;
+ private readonly DispatcherTimer _timer;
private BitmapImage? _deviceImage;
private ArtemisDevice? _oldDevice;
+ private List _highlightedLeds;
+ private List _dimmedLeds;
///
/// Creates a new instance of the class
@@ -50,9 +54,10 @@ namespace Artemis.UI.Shared
{
_backingStore = new DrawingGroup();
_deviceVisualizerLeds = new List();
+ _dimmedLeds = new List();
// Run an update timer at 25 fps
- _timer = new Timer(40);
+ _timer = new DispatcherTimer(DispatcherPriority.Render) {Interval = TimeSpan.FromMilliseconds(40)};
MouseLeftButtonUp += OnMouseLeftButtonUp;
Loaded += OnLoaded;
@@ -80,9 +85,9 @@ namespace Artemis.UI.Shared
///
/// Gets or sets a list of LEDs to highlight
///
- public IEnumerable? HighlightedLeds
+ public ObservableCollection? HighlightedLeds
{
- get => (IEnumerable) GetValue(HighlightedLedsProperty);
+ get => (ObservableCollection) GetValue(HighlightedLedsProperty);
set => SetValue(HighlightedLedsProperty, value);
}
@@ -158,10 +163,9 @@ namespace Artemis.UI.Shared
protected virtual void Dispose(bool disposing)
{
if (disposing)
- _timer.Dispose();
+ _timer.Stop();
}
-
private static Size ResizeKeepAspect(Size src, double maxWidth, double maxHeight)
{
double scale;
@@ -191,8 +195,10 @@ namespace Artemis.UI.Shared
private void OnUnloaded(object? sender, RoutedEventArgs e)
{
_timer.Stop();
- _timer.Elapsed -= TimerOnTick;
+ _timer.Tick -= TimerOnTick;
+ if (HighlightedLeds != null)
+ HighlightedLeds.CollectionChanged -= HighlightedLedsChanged;
if (_oldDevice != null)
{
if (Device != null)
@@ -223,16 +229,34 @@ namespace Artemis.UI.Shared
private void OnLoaded(object? sender, RoutedEventArgs e)
{
_timer.Start();
- _timer.Elapsed += TimerOnTick;
+ _timer.Tick += TimerOnTick;
}
private void TimerOnTick(object? sender, EventArgs e)
{
- Execute.PostToUIThread(() =>
+ if (ShowColors && Visibility == Visibility.Visible)
+ Render();
+ }
+
+ private void Render()
+ {
+ DrawingContext drawingContext = _backingStore.Append();
+
+ if (_highlightedLeds.Any())
{
- if (ShowColors && Visibility == Visibility.Visible)
- Render();
- });
+ foreach (DeviceVisualizerLed deviceVisualizerLed in _highlightedLeds)
+ deviceVisualizerLed.RenderColor(_backingStore, drawingContext, false);
+
+ foreach (DeviceVisualizerLed deviceVisualizerLed in _dimmedLeds)
+ deviceVisualizerLed.RenderColor(_backingStore, drawingContext, true);
+ }
+ else
+ {
+ foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
+ deviceVisualizerLed.RenderColor(_backingStore, drawingContext, false);
+ }
+
+ drawingContext.Close();
}
private void UpdateTransform()
@@ -241,22 +265,12 @@ namespace Artemis.UI.Shared
InvalidateMeasure();
}
- private static void DevicePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- DeviceVisualizer deviceVisualizer = (DeviceVisualizer) d;
- deviceVisualizer.Dispatcher.Invoke(() => { deviceVisualizer.SetupForDevice(); });
- }
-
- private static void ShowColorsPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- DeviceVisualizer deviceVisualizer = (DeviceVisualizer) d;
- deviceVisualizer.Dispatcher.Invoke(() => { deviceVisualizer.SetupForDevice(); });
- }
-
private void SetupForDevice()
{
_deviceImage = null;
_deviceVisualizerLeds.Clear();
+ _highlightedLeds = new List();
+ _dimmedLeds = new List();
if (Device == null)
return;
@@ -319,40 +333,47 @@ namespace Artemis.UI.Shared
private void DeviceUpdated(object? sender, EventArgs e)
{
- Execute.PostToUIThread(SetupForDevice);
+ Dispatcher.Invoke(SetupForDevice);
}
private void DevicePropertyChanged(object? sender, PropertyChangedEventArgs e)
{
- Execute.PostToUIThread(SetupForDevice);
+ Dispatcher.Invoke(SetupForDevice);
}
- private void Render()
+ private static void DevicePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
- DrawingContext drawingContext = _backingStore.Append();
+ DeviceVisualizer deviceVisualizer = (DeviceVisualizer) d;
+ deviceVisualizer.Dispatcher.Invoke(() => { deviceVisualizer.SetupForDevice(); });
+ }
- if (HighlightedLeds != null && HighlightedLeds.Any())
+ private static void ShowColorsPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ DeviceVisualizer deviceVisualizer = (DeviceVisualizer) d;
+ deviceVisualizer.Dispatcher.Invoke(() => { deviceVisualizer.SetupForDevice(); });
+ }
+
+ private static void HighlightedLedsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ DeviceVisualizer deviceVisualizer = (DeviceVisualizer) d;
+ if (e.OldValue is ObservableCollection oldCollection)
+ oldCollection.CollectionChanged -= deviceVisualizer.HighlightedLedsChanged;
+ if (e.NewValue is ObservableCollection newCollection)
+ newCollection.CollectionChanged += deviceVisualizer.HighlightedLedsChanged;
+ }
+
+ private void HighlightedLedsChanged(object? sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
+ {
+ if (HighlightedLeds != null)
{
- // Faster on large devices, maybe a bit slower on smaller ones but that's ok
- ILookup toHighlight = HighlightedLeds.ToLookup(l => l);
- foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
- deviceVisualizerLed.RenderColor(_backingStore, drawingContext, !toHighlight.Contains(deviceVisualizerLed.Led));
+ _highlightedLeds = _deviceVisualizerLeds.Where(l => HighlightedLeds.Contains(l.Led)).ToList();
+ _dimmedLeds = _deviceVisualizerLeds.Where(l => !HighlightedLeds.Contains(l.Led)).ToList();
}
else
{
- foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
- deviceVisualizerLed.RenderColor(_backingStore, drawingContext, false);
+ _highlightedLeds = new List();
+ _dimmedLeds = new List();
}
-
- drawingContext.Close();
- }
-
-
- ///
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs
index f3f3626ea..282d63116 100644
--- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs
+++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs
@@ -12,7 +12,6 @@ using Ninject.Parameters;
using Serilog;
using SkiaSharp;
using SkiaSharp.Views.WPF;
-using Stylet;
namespace Artemis.UI.Shared.Services
{
@@ -45,9 +44,11 @@ namespace Artemis.UI.Shared.Services
private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
{
- if (!_doTick) return;
+ if (!_doTick)
+ return;
_doTick = false;
- Execute.PostToUIThread(OnProfilePreviewUpdated);
+
+ OnProfilePreviewUpdated();
}
private void ReloadProfile()
@@ -144,6 +145,7 @@ namespace Artemis.UI.Shared.Services
{
if (_currentTime.Equals(value)) return;
_currentTime = value;
+
Tick();
OnCurrentTimeChanged();
}
diff --git a/src/Artemis.UI.Shared/Utilities/HitTestUtilities.cs b/src/Artemis.UI.Shared/Utilities/HitTestUtilities.cs
index 5a76bde26..7d1c99254 100644
--- a/src/Artemis.UI.Shared/Utilities/HitTestUtilities.cs
+++ b/src/Artemis.UI.Shared/Utilities/HitTestUtilities.cs
@@ -25,7 +25,8 @@ namespace Artemis.UI.Shared
HitTestResultBehavior ResultCallback(HitTestResult r) => HitTestResultBehavior.Continue;
HitTestFilterBehavior FilterCallback(DependencyObject e)
{
- if (e is FrameworkElement fe && fe.DataContext is T context && !result.Contains(context)) result.Add(context);
+ if (e is FrameworkElement fe && fe.DataContext is T context && !result.Contains(context))
+ result.Add(context);
return HitTestFilterBehavior.Continue;
}
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs
index 2b748fc01..95af67ace 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs
@@ -583,30 +583,28 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
return;
}
- Execute.PostToUIThread(() =>
+ TimeSpan newTime = ProfileEditorService.CurrentTime.Add(TimeSpan.FromSeconds(e.DeltaTime));
+ if (SelectedProfileElement != null)
{
- TimeSpan newTime = ProfileEditorService.CurrentTime.Add(TimeSpan.FromSeconds(e.DeltaTime));
- if (SelectedProfileElement != null)
+ if (Repeating && RepeatTimeline)
{
- if (Repeating && RepeatTimeline)
- {
- if (newTime > SelectedProfileElement.Timeline.Length)
- newTime = TimeSpan.Zero;
- }
- else if (Repeating && RepeatSegment)
- {
- if (newTime > GetCurrentSegmentEnd())
- newTime = GetCurrentSegmentStart();
- }
- else if (newTime > SelectedProfileElement.Timeline.Length)
- {
- newTime = SelectedProfileElement.Timeline.Length;
- Pause();
- }
+ if (newTime > SelectedProfileElement.Timeline.Length)
+ newTime = TimeSpan.Zero;
}
+ else if (Repeating && RepeatSegment)
+ {
+ if (newTime > GetCurrentSegmentEnd())
+ newTime = GetCurrentSegmentStart();
+ }
+ else if (newTime > SelectedProfileElement.Timeline.Length)
+ {
+ newTime = SelectedProfileElement.Timeline.Length;
+ Pause();
+ }
+ }
- ProfileEditorService.CurrentTime = newTime;
- });
+ // Update current time on high priority to keep things buttery smooth as if you're using the mouse ༼ つ ◕_◕ ༽つ
+ Execute.OnUIThreadSync(() => ProfileEditorService.CurrentTime = newTime);
}
#endregion
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLayerViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLayerViewModel.cs
index 85f01fd94..f37a9d081 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLayerViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileLayerViewModel.cs
@@ -8,6 +8,8 @@ using Artemis.UI.Extensions;
using Artemis.UI.Screens.Shared;
using Artemis.UI.Services;
using Artemis.UI.Shared.Services;
+using SkiaSharp;
+using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.Visualization
{
@@ -95,6 +97,16 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
CreateViewportRectangle();
}
+ #region Updating
+
+ private LayerShape _lastShape;
+ private Rect _lastBounds;
+
+ private SKPoint _lastAnchor;
+ private SKPoint _lastPosition;
+ private double _lastRotation;
+ private SKSize _lastScale;
+
private void CreateShapeGeometry()
{
if (Layer.LayerShape == null || !Layer.Leds.Any())
@@ -104,21 +116,22 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
}
Rect bounds = _layerEditorService.GetLayerBounds(Layer);
- Geometry shapeGeometry = Geometry.Empty;
- switch (Layer.LayerShape)
+
+ // Only bother the UI thread for both of these if we're sure
+ if (HasShapeChanged(bounds))
{
- case EllipseShape _:
- shapeGeometry = new EllipseGeometry(bounds);
- break;
- case RectangleShape _:
- shapeGeometry = new RectangleGeometry(bounds);
- break;
+ Execute.OnUIThreadSync(() =>
+ {
+ if (Layer.LayerShape is RectangleShape)
+ ShapeGeometry = new RectangleGeometry(bounds);
+ if (Layer.LayerShape is EllipseShape)
+ ShapeGeometry = new EllipseGeometry(bounds);
+ });
+ }
+ if ((Layer.LayerBrush == null || Layer.LayerBrush.SupportsTransformation) && HasTransformationChanged())
+ {
+ Execute.OnUIThreadSync(() => ShapeGeometry.Transform = _layerEditorService.GetLayerTransformGroup(Layer));
}
-
- if (Layer.LayerBrush == null || Layer.LayerBrush.SupportsTransformation)
- shapeGeometry.Transform = _layerEditorService.GetLayerTransformGroup(Layer);
-
- ShapeGeometry = shapeGeometry;
}
private void CreateViewportRectangle()
@@ -132,44 +145,30 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization
ViewportRectangle = _layerEditorService.GetLayerBounds(Layer);
}
- private Geometry CreateRectangleGeometry(ArtemisLed led)
+ private bool HasShapeChanged(Rect bounds)
{
- Rect rect = led.RgbLed.AbsoluteBoundary.ToWindowsRect(1);
- return new RectangleGeometry(rect);
+ bool result = !Equals(_lastBounds, bounds) || !Equals(_lastShape, Layer.LayerShape);
+ _lastShape = Layer.LayerShape;
+ _lastBounds = bounds;
+ return result;
}
- private Geometry CreateCircleGeometry(ArtemisLed led)
+ private bool HasTransformationChanged()
{
- Rect rect = led.RgbLed.AbsoluteBoundary.ToWindowsRect(1);
- return new EllipseGeometry(rect);
+ bool result = _lastAnchor != Layer.Transform.AnchorPoint.CurrentValue ||
+ _lastPosition != Layer.Transform.Position.CurrentValue ||
+ _lastRotation != Layer.Transform.Rotation.CurrentValue ||
+ _lastScale != Layer.Transform.Scale.CurrentValue;
+
+ _lastAnchor = Layer.Transform.AnchorPoint.CurrentValue;
+ _lastPosition = Layer.Transform.Position.CurrentValue;
+ _lastRotation = Layer.Transform.Rotation.CurrentValue;
+ _lastScale = Layer.Transform.Scale.CurrentValue;
+
+ return result;
}
- private Geometry CreateCustomGeometry(ArtemisLed led, double deflateAmount)
- {
- Rect rect = led.RgbLed.AbsoluteBoundary.ToWindowsRect(1);
- try
- {
- PathGeometry geometry = Geometry.Combine(
- Geometry.Empty,
- Geometry.Parse(led.RgbLed.ShapeData),
- GeometryCombineMode.Union,
- new TransformGroup
- {
- Children = new TransformCollection
- {
- new ScaleTransform(rect.Width, rect.Height),
- new TranslateTransform(rect.X, rect.Y)
- }
- }
- );
-
- return geometry;
- }
- catch (Exception)
- {
- return CreateRectangleGeometry(led);
- }
- }
+ #endregion
#region Overrides of Screen
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/Tools/EditToolViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/Tools/EditToolViewModel.cs
index 8e85a3b1f..3f0e8fbde 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/Tools/EditToolViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/Tools/EditToolViewModel.cs
@@ -51,7 +51,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization.Tools
private void Update()
{
- if (!(ProfileEditorService.SelectedProfileElement is Layer layer))
+ if (ProfileEditorService.SelectedProfileElement is not Layer layer)
return;
ShapePath = _layerEditorService.GetLayerPath(layer, true, true, true);
diff --git a/src/Artemis.UI/Screens/Shared/PanZoomViewModel.cs b/src/Artemis.UI/Screens/Shared/PanZoomViewModel.cs
index d1e3d88fe..c7e241fa8 100644
--- a/src/Artemis.UI/Screens/Shared/PanZoomViewModel.cs
+++ b/src/Artemis.UI/Screens/Shared/PanZoomViewModel.cs
@@ -152,11 +152,6 @@ namespace Artemis.UI.Screens.Shared
PanY = rect.Top * -1 * Zoom;
}
- public Rect TransformContainingRect(Rectangle rect)
- {
- return TransformContainingRect(rect.ToWindowsRect(1));
- }
-
public Rect TransformContainingRect(Rect rect)
{
// Create the same transform group the view is using
@@ -168,6 +163,17 @@ namespace Artemis.UI.Screens.Shared
return transformGroup.TransformBounds(rect);
}
+ public Rect UnTransformContainingRect(Rect rect)
+ {
+ // Create the same transform group the view is using
+ TransformGroup transformGroup = new();
+ transformGroup.Children.Add(new TranslateTransform(PanX * -1, PanY * -1));
+ transformGroup.Children.Add(new ScaleTransform(1 / Zoom, 1 / Zoom));
+
+ // Apply it to the device rect
+ return transformGroup.TransformBounds(rect);
+ }
+
public Point GetRelativeMousePosition(object container, MouseEventArgs e)
{
// Get the mouse position relative to the panned / zoomed panel, not very MVVM but I can't find a better way
diff --git a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.xaml b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.xaml
index d569d59d4..c4fed7122 100644
--- a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.xaml
+++ b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.xaml
@@ -188,8 +188,7 @@