mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Profile tree - Fixed potential crash when dragging files over Artemis Module reorder - Fixed potential crash when dragging files over Artemis
308 lines
12 KiB
C#
308 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Timers;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Imaging;
|
|
using Artemis.Core;
|
|
using Stylet;
|
|
|
|
namespace Artemis.UI.Shared
|
|
{
|
|
/// <summary>
|
|
/// Visualizes an <see cref="ArtemisDevice" /> with optional per-LED colors
|
|
/// </summary>
|
|
public class DeviceVisualizer : FrameworkElement, IDisposable
|
|
{
|
|
/// <summary>
|
|
/// The device to visualize
|
|
/// </summary>
|
|
public static readonly DependencyProperty DeviceProperty = DependencyProperty.Register(nameof(Device), typeof(ArtemisDevice), typeof(DeviceVisualizer),
|
|
new FrameworkPropertyMetadata(default(ArtemisDevice), FrameworkPropertyMetadataOptions.AffectsRender, DevicePropertyChangedCallback));
|
|
|
|
/// <summary>
|
|
/// Whether or not to show per-LED colors
|
|
/// </summary>
|
|
public static readonly DependencyProperty ShowColorsProperty = DependencyProperty.Register(nameof(ShowColors), typeof(bool), typeof(DeviceVisualizer),
|
|
new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.AffectsRender, ShowColorsPropertyChangedCallback));
|
|
|
|
/// <summary>
|
|
/// A list of LEDs to highlight
|
|
/// </summary>
|
|
public static readonly DependencyProperty HighlightedLedsProperty = DependencyProperty.Register(nameof(HighlightedLeds), typeof(IEnumerable<ArtemisLed>), typeof(DeviceVisualizer),
|
|
new FrameworkPropertyMetadata(default(IEnumerable<ArtemisLed>)));
|
|
|
|
private readonly DrawingGroup _backingStore;
|
|
private readonly List<DeviceVisualizerLed> _deviceVisualizerLeds;
|
|
private readonly Timer _timer;
|
|
private BitmapImage? _deviceImage;
|
|
private ArtemisDevice? _oldDevice;
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of the <see cref="DeviceVisualizer" /> class
|
|
/// </summary>
|
|
public DeviceVisualizer()
|
|
{
|
|
_backingStore = new DrawingGroup();
|
|
_deviceVisualizerLeds = new List<DeviceVisualizerLed>();
|
|
|
|
// Run an update timer at 25 fps
|
|
_timer = new Timer(40);
|
|
_timer.Elapsed += TimerOnTick;
|
|
|
|
Loaded += OnLoaded;
|
|
Unloaded += OnUnloaded;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the device to visualize
|
|
/// </summary>
|
|
public ArtemisDevice? Device
|
|
{
|
|
get => (ArtemisDevice) GetValue(DeviceProperty);
|
|
set => SetValue(DeviceProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets whether or not to show per-LED colors
|
|
/// </summary>
|
|
public bool ShowColors
|
|
{
|
|
get => (bool) GetValue(ShowColorsProperty);
|
|
set => SetValue(ShowColorsProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a list of LEDs to highlight
|
|
/// </summary>
|
|
public IEnumerable<ArtemisLed>? HighlightedLeds
|
|
{
|
|
get => (IEnumerable<ArtemisLed>) GetValue(HighlightedLedsProperty);
|
|
set => SetValue(HighlightedLedsProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
|
|
/// </summary>
|
|
/// <param name="disposing">
|
|
/// <see langword="true" /> to release both managed and unmanaged resources;
|
|
/// <see langword="false" /> to release only unmanaged resources.
|
|
/// </param>
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (disposing) _timer.Stop();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void OnRender(DrawingContext drawingContext)
|
|
{
|
|
if (Device == null)
|
|
return;
|
|
|
|
// Determine the scale required to fit the desired size of the control
|
|
Size measureSize = MeasureDevice();
|
|
double scale = Math.Min(DesiredSize.Width / measureSize.Width, DesiredSize.Height / measureSize.Height);
|
|
Rect scaledRect = new Rect(0, 0, measureSize.Width * scale, measureSize.Height * scale);
|
|
|
|
// Center and scale the visualization in the desired bounding box
|
|
if (DesiredSize.Width > 0 && DesiredSize.Height > 0)
|
|
{
|
|
drawingContext.PushTransform(new TranslateTransform(DesiredSize.Width / 2 - scaledRect.Width / 2, DesiredSize.Height / 2 - scaledRect.Height / 2));
|
|
drawingContext.PushTransform(new ScaleTransform(scale, scale));
|
|
}
|
|
|
|
// Determine the offset required to rotate within bounds
|
|
Rect rotationRect = new Rect(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height);
|
|
rotationRect.Transform(new RotateTransform(Device.Rotation).Value);
|
|
|
|
// Apply device rotation
|
|
drawingContext.PushTransform(new TranslateTransform(0 - rotationRect.Left, 0 - rotationRect.Top));
|
|
drawingContext.PushTransform(new RotateTransform(Device.Rotation));
|
|
|
|
// Apply device scale
|
|
drawingContext.PushTransform(new ScaleTransform(Device.Scale, Device.Scale));
|
|
|
|
// Render device and LED images
|
|
if (_deviceImage != null)
|
|
drawingContext.DrawImage(_deviceImage, new Rect(0, 0, Device.RgbDevice.Size.Width, Device.RgbDevice.Size.Height));
|
|
|
|
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
|
|
deviceVisualizerLed.RenderImage(drawingContext);
|
|
|
|
drawingContext.DrawDrawing(_backingStore);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override Size MeasureOverride(Size availableSize)
|
|
{
|
|
if (Device == null)
|
|
return Size.Empty;
|
|
|
|
Size deviceSize = MeasureDevice();
|
|
if (deviceSize.Width <= 0 || deviceSize.Height <= 0)
|
|
return Size.Empty;
|
|
|
|
return ResizeKeepAspect(deviceSize, availableSize.Width, availableSize.Height);
|
|
}
|
|
|
|
private static Size ResizeKeepAspect(Size src, double maxWidth, double maxHeight)
|
|
{
|
|
double scale;
|
|
if (double.IsPositiveInfinity(maxWidth) && !double.IsPositiveInfinity(maxHeight))
|
|
scale = maxHeight / src.Height;
|
|
else if (!double.IsPositiveInfinity(maxWidth) && double.IsPositiveInfinity(maxHeight))
|
|
scale = maxWidth / src.Width;
|
|
else if (double.IsPositiveInfinity(maxWidth) && double.IsPositiveInfinity(maxHeight))
|
|
return src;
|
|
else
|
|
scale = Math.Min(maxWidth / src.Width, maxHeight / src.Height);
|
|
|
|
return new Size(src.Width * scale, src.Height * scale);
|
|
}
|
|
|
|
private Size MeasureDevice()
|
|
{
|
|
if (Device == null)
|
|
return Size.Empty;
|
|
|
|
Rect rotationRect = new Rect(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height);
|
|
rotationRect.Transform(new RotateTransform(Device.Rotation).Value);
|
|
|
|
return rotationRect.Size;
|
|
}
|
|
|
|
private void OnUnloaded(object? sender, RoutedEventArgs e)
|
|
{
|
|
_timer.Stop();
|
|
|
|
if (_oldDevice != null)
|
|
{
|
|
if (Device != null)
|
|
Device.RgbDevice.PropertyChanged -= DevicePropertyChanged;
|
|
_oldDevice = null;
|
|
}
|
|
}
|
|
|
|
private void OnLoaded(object? sender, RoutedEventArgs e)
|
|
{
|
|
_timer.Start();
|
|
}
|
|
|
|
private void TimerOnTick(object? sender, EventArgs e)
|
|
{
|
|
Execute.PostToUIThread(() =>
|
|
{
|
|
if (ShowColors && Visibility == Visibility.Visible)
|
|
Render();
|
|
});
|
|
}
|
|
|
|
private void UpdateTransform()
|
|
{
|
|
InvalidateVisual();
|
|
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();
|
|
|
|
if (Device == null)
|
|
return;
|
|
|
|
if (_oldDevice != null)
|
|
Device.RgbDevice.PropertyChanged -= DevicePropertyChanged;
|
|
_oldDevice = Device;
|
|
|
|
Device.RgbDevice.PropertyChanged += DevicePropertyChanged;
|
|
UpdateTransform();
|
|
|
|
// Load the device main image
|
|
if (Device.RgbDevice.DeviceInfo?.Image?.AbsolutePath != null && File.Exists(Device.RgbDevice.DeviceInfo.Image.AbsolutePath))
|
|
_deviceImage = new BitmapImage(Device.RgbDevice.DeviceInfo.Image);
|
|
|
|
// Create all the LEDs
|
|
foreach (ArtemisLed artemisLed in Device.Leds)
|
|
_deviceVisualizerLeds.Add(new DeviceVisualizerLed(artemisLed));
|
|
|
|
if (!ShowColors)
|
|
{
|
|
InvalidateMeasure();
|
|
return;
|
|
}
|
|
|
|
// Create the opacity drawing group
|
|
DrawingGroup opacityDrawingGroup = new DrawingGroup();
|
|
DrawingContext drawingContext = opacityDrawingGroup.Open();
|
|
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
|
|
deviceVisualizerLed.RenderOpacityMask(drawingContext);
|
|
drawingContext.Close();
|
|
|
|
// Render the store as a bitmap
|
|
DrawingImage drawingImage = new DrawingImage(opacityDrawingGroup);
|
|
Image image = new Image {Source = drawingImage};
|
|
RenderTargetBitmap bitmap = new RenderTargetBitmap(
|
|
Math.Max(1, (int) (opacityDrawingGroup.Bounds.Width * 2.5)),
|
|
Math.Max(1, (int) (opacityDrawingGroup.Bounds.Height * 2.5)),
|
|
96,
|
|
96,
|
|
PixelFormats.Pbgra32
|
|
);
|
|
image.Arrange(new Rect(0, 0, bitmap.Width, bitmap.Height));
|
|
bitmap.Render(image);
|
|
bitmap.Freeze();
|
|
|
|
// Set the bitmap as the opacity mask for the colors backing store
|
|
ImageBrush bitmapBrush = new ImageBrush(bitmap);
|
|
bitmapBrush.Freeze();
|
|
_backingStore.OpacityMask = bitmapBrush;
|
|
|
|
InvalidateMeasure();
|
|
}
|
|
|
|
private void DevicePropertyChanged(object? sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == nameof(Device.RgbDevice.Scale) || e.PropertyName == nameof(Device.RgbDevice.Rotation))
|
|
UpdateTransform();
|
|
}
|
|
|
|
|
|
private void Render()
|
|
{
|
|
DrawingContext drawingContext = _backingStore.Open();
|
|
|
|
if (HighlightedLeds != null && HighlightedLeds.Any())
|
|
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
|
|
deviceVisualizerLed.RenderColor(drawingContext, !HighlightedLeds.Contains(deviceVisualizerLed.Led));
|
|
else
|
|
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
|
|
deviceVisualizerLed.RenderColor(drawingContext, false);
|
|
|
|
drawingContext.Close();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
}
|
|
} |