UI - Added reusable device visualizer

Settings - Show device LED images in settings
Editor - Improved performance on big surfaces
This commit is contained in:
SpoinkyNL committed 2020-04-15 19:50:46 +02:00
1 parent 8a45039863
commit efa0f28231
31 files changed
+407 -402

No files matched your search

@@ -26,10 +26,12 @@
<PackageReference Include="Ninject" Version="3.3.4" />
<PackageReference Include="Ninject.Extensions.Conventions" Version="3.3.0" />
<PackageReference Include="SkiaSharp" Version="1.68.2-preview.29" />
<PackageReference Include="SkiaSharp.Views.WPF" Version="1.68.2-preview.29" />
<PackageReference Include="Stylet" Version="1.3.1" />
<PackageReference Include="System.Buffers" Version="4.5.0" />
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.0" />
<PackageReference Include="WriteableBitmapEx" Version="1.6.5" />
</ItemGroup>
<ItemGroup>
<Compile Remove="obj\x64\Debug\ColorPicker.g.cs" />
@@ -50,6 +52,11 @@
<Private>false</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Reference Include="RGB.NET.Core">
<HintPath>..\..\..\RGB.NET\bin\netstandard2.0\RGB.NET.Core.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\Fonts\RobotoMono-Regular.ttf" />
</ItemGroup>
@@ -4,7 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters" x:Class="Artemis.UI.Shared.ColorPicker"
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters" x:Class="Artemis.UI.Shared.Controls.ColorPicker"
mc:Ignorable="d"
d:DesignHeight="101.848" d:DesignWidth="242.956">
<UserControl.Resources>
@@ -5,7 +5,7 @@ using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace Artemis.UI.Shared
namespace Artemis.UI.Shared.Controls
{
/// <summary>
/// Interaction logic for ColorPicker.xaml
@@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Artemis.Core.Models.Surface;
using RGB.NET.Core;
using Stylet;
namespace Artemis.UI.Shared.Controls
{
public class DeviceVisualizer : FrameworkElement
{
public static readonly DependencyProperty DeviceProperty = DependencyProperty.Register(nameof(Device), typeof(ArtemisDevice), typeof(DeviceVisualizer),
new FrameworkPropertyMetadata(default(ArtemisDevice), FrameworkPropertyMetadataOptions.AffectsRender, DevicePropertyChangedCallback));
public static readonly DependencyProperty ShowColorsProperty = DependencyProperty.Register(nameof(ShowColors), typeof(bool), typeof(DeviceVisualizer),
new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.AffectsRender));
private BitmapImage _deviceImage;
private List<DeviceVisualizerLed> _deviceVisualizerLeds;
private DrawingGroup _backingStore;
private static void DevicePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var deviceVisualizer = (DeviceVisualizer) d;
deviceVisualizer.Dispatcher.Invoke(() =>
{
deviceVisualizer.SubscribeToSurfaceUpdate((ArtemisDevice) e.OldValue, (ArtemisDevice) e.NewValue);
deviceVisualizer.Initialize();
});
}
public ArtemisDevice Device
{
get => (ArtemisDevice) GetValue(DeviceProperty);
set => SetValue(DeviceProperty, value);
}
public bool ShowColors
{
get => (bool) GetValue(ShowColorsProperty);
set => SetValue(ShowColorsProperty, value);
}
public DeviceVisualizer()
{
_backingStore = new DrawingGroup();
_deviceVisualizerLeds = new List<DeviceVisualizerLed>();
}
private void Initialize()
{
_deviceImage = null;
_deviceVisualizerLeds.Clear();
if (Device == null)
return;
// 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 (var artemisLed in Device.Leds)
{
_deviceVisualizerLeds.Add(new DeviceVisualizerLed(artemisLed));
}
}
private void SubscribeToSurfaceUpdate(ArtemisDevice oldValue, ArtemisDevice newValue)
{
if (oldValue != null)
oldValue.Surface.RgbSurface.Updated -= RgbSurfaceOnUpdated;
if (newValue != null)
newValue.Surface.RgbSurface.Updated += RgbSurfaceOnUpdated;
}
private void RgbSurfaceOnUpdated(UpdatedEventArgs args)
{
Dispatcher.Invoke(() =>
{
if (ShowColors)
{
Render();
}
});
}
private void Render()
{
var drawingContext = _backingStore.Open();
foreach (var deviceVisualizerLed in _deviceVisualizerLeds)
deviceVisualizerLed.Render(drawingContext, true);
drawingContext.Close();
}
protected override void OnRender(DrawingContext drawingContext)
{
if (Device == null)
return;
// Determine the scale required to fit the desired size of the control
var scale = Math.Min(DesiredSize.Width / Device.RgbDevice.Size.Width, DesiredSize.Height / Device.RgbDevice.Size.Height);
var scaledRect = new Rect(0, 0, Device.RgbDevice.Size.Width * scale, Device.RgbDevice.Size.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));
}
// Render device and LED images
if (_deviceImage != null)
drawingContext.DrawImage(_deviceImage, new Rect(0, 0, Device.RgbDevice.Size.Width, Device.RgbDevice.Size.Height));
foreach (var deviceVisualizerLed in _deviceVisualizerLeds)
deviceVisualizerLed.Render(drawingContext, false);
drawingContext.DrawDrawing(_backingStore);
}
}
}
@@ -0,0 +1,135 @@
using System;
using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Artemis.Core.Models.Surface;
using RGB.NET.Core;
using Color = System.Windows.Media.Color;
namespace Artemis.UI.Shared.Controls
{
internal class DeviceVisualizerLed
{
public DeviceVisualizerLed(ArtemisLed led)
{
Led = led;
if (Led.RgbLed.Image != null && File.Exists(Led.RgbLed.Image.AbsolutePath))
LedImage = new BitmapImage(Led.RgbLed.Image);
CreateLedGeometry();
}
public ArtemisLed Led { get; }
public BitmapImage LedImage { get; set; }
public Geometry DisplayGeometry { get; private set; }
internal void Render(DrawingContext drawingContext, bool renderGeometry)
{
if (!renderGeometry)
RenderImage(drawingContext);
else
RenderGeometry(drawingContext);
}
private void CreateLedGeometry()
{
switch (Led.RgbLed.Shape)
{
case Shape.Custom:
if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad)
CreateCustomGeometry(2.0);
else
CreateCustomGeometry(1.0);
break;
case Shape.Rectangle:
if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad)
CreateKeyCapGeometry();
else
CreateRectangleGeometry();
break;
case Shape.Circle:
CreateCircleGeometry();
break;
default:
throw new ArgumentOutOfRangeException();
}
// Stroke geometry is the display geometry excluding the inner geometry
DisplayGeometry.Transform = new TranslateTransform(Led.RgbLed.LedRectangle.Location.X, Led.RgbLed.LedRectangle.Location.Y);
// Try to gain some performance
DisplayGeometry = DisplayGeometry.GetFlattenedPathGeometry();
DisplayGeometry.Freeze();
}
private void CreateRectangleGeometry()
{
DisplayGeometry = new RectangleGeometry(new Rect(0.5, 0.5, Led.RgbLed.Size.Width - 1, Led.RgbLed.Size.Height - 1));
}
private void CreateCircleGeometry()
{
DisplayGeometry = new EllipseGeometry(new Rect(0.5, 0.5, Led.RgbLed.Size.Width - 1, Led.RgbLed.Size.Height - 1));
}
private void CreateKeyCapGeometry()
{
DisplayGeometry = new RectangleGeometry(new Rect(1, 1, Led.RgbLed.Size.Width - 2, Led.RgbLed.Size.Height - 2), 1.6, 1.6);
}
private void CreateCustomGeometry(double deflateAmount)
{
try
{
DisplayGeometry = Geometry.Combine(
Geometry.Empty,
Geometry.Parse(Led.RgbLed.ShapeData),
GeometryCombineMode.Union,
new TransformGroup
{
Children = new TransformCollection
{
new ScaleTransform(Led.RgbLed.Size.Width - deflateAmount, Led.RgbLed.Size.Height - deflateAmount),
new TranslateTransform(deflateAmount / 2, deflateAmount / 2)
}
}
);
}
catch (Exception)
{
CreateRectangleGeometry();
}
}
private void RenderGeometry(DrawingContext drawingContext)
{
if (DisplayGeometry == null)
return;
var r = Led.RgbLed.Color.GetR();
var g = Led.RgbLed.Color.GetG();
var b = Led.RgbLed.Color.GetB();
var fillBrush = new SolidColorBrush(Color.FromArgb(100, r,g,b));
fillBrush.Freeze();
var penBrush = new SolidColorBrush(Color.FromArgb(255, r, g, b));
penBrush.Freeze();
drawingContext.DrawGeometry(fillBrush, new Pen(penBrush, 1), DisplayGeometry);
}
private void RenderImage(DrawingContext drawingContext)
{
if (LedImage == null)
return;
var ledRect = new Rect(
Led.RgbLed.LedRectangle.Location.X,
Led.RgbLed.LedRectangle.Location.Y,
Led.RgbLed.LedRectangle.Size.Width,
Led.RgbLed.LedRectangle.Size.Height
);
drawingContext.DrawImage(LedImage, ledRect);
}
}
}
@@ -1,4 +1,4 @@
<UserControl x:Class="Artemis.UI.Shared.DraggableFloat"
<UserControl x:Class="Artemis.UI.Shared.Controls.DraggableFloat"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -5,7 +5,7 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace Artemis.UI.Shared
namespace Artemis.UI.Shared.Controls
{
/// <summary>
/// Interaction logic for DraggableFloat.xaml
@@ -1,9 +1,8 @@
<UserControl x:Class="Artemis.UI.Shared.GradientPicker"
<UserControl x:Class="Artemis.UI.Shared.Controls.GradientPicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Shared"
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
@@ -8,7 +8,7 @@ using Artemis.Core.Models.Profile;
using Artemis.UI.Shared.Annotations;
using Artemis.UI.Shared.Services.Interfaces;
namespace Artemis.UI.Shared
namespace Artemis.UI.Shared.Controls
{
/// <summary>
/// Interaction logic for GradientPicker.xaml
@@ -9,6 +9,7 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:shared="clr-namespace:Artemis.UI.Shared"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:controls1="clr-namespace:Artemis.UI.Shared.Controls"
mc:Ignorable="d"
Title="Gradient Editor"
Background="{DynamicResource MaterialDesignPaper}"
@@ -80,7 +81,7 @@
</Grid.ColumnDefinitions>
<Label Grid.Column="0" HorizontalAlignment="Right">Color:</Label>
<shared:ColorPicker
<controls1:ColorPicker
Grid.Row="0"
Grid.Column="1"
x:Name="CurrentColor"
@@ -100,6 +100,7 @@ namespace Artemis.UI.Shared.Services.Dialog
{
try
{
DialogHost.CloseDialogCommand.Execute(new object(), null);
await ShowDialog<ExceptionDialogViewModel>(arguments);
}
catch (Exception)
@@ -122,7 +123,7 @@ namespace Artemis.UI.Shared.Services.Dialog
else
result = DialogHost.Show(view, identifier, viewModel.OnDialogOpened, viewModel.OnDialogClosed);
});
return await result;
}
}