1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

UI - Added reusable device visualizer

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

View File

@ -26,10 +26,12 @@
<PackageReference Include="Ninject" Version="3.3.4" /> <PackageReference Include="Ninject" Version="3.3.4" />
<PackageReference Include="Ninject.Extensions.Conventions" Version="3.3.0" /> <PackageReference Include="Ninject.Extensions.Conventions" Version="3.3.0" />
<PackageReference Include="SkiaSharp" Version="1.68.2-preview.29" /> <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="Stylet" Version="1.3.1" />
<PackageReference Include="System.Buffers" Version="4.5.0" /> <PackageReference Include="System.Buffers" Version="4.5.0" />
<PackageReference Include="System.Numerics.Vectors" 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="System.Runtime.CompilerServices.Unsafe" Version="4.7.0" />
<PackageReference Include="WriteableBitmapEx" Version="1.6.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Remove="obj\x64\Debug\ColorPicker.g.cs" /> <Compile Remove="obj\x64\Debug\ColorPicker.g.cs" />
@ -50,6 +52,11 @@
<Private>false</Private> <Private>false</Private>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Reference Include="RGB.NET.Core">
<HintPath>..\..\..\RGB.NET\bin\netstandard2.0\RGB.NET.Core.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup> <ItemGroup>
<Resource Include="Resources\Fonts\RobotoMono-Regular.ttf" /> <Resource Include="Resources\Fonts\RobotoMono-Regular.ttf" />
</ItemGroup> </ItemGroup>

View File

@ -4,7 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" 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" mc:Ignorable="d"
d:DesignHeight="101.848" d:DesignWidth="242.956"> d:DesignHeight="101.848" d:DesignWidth="242.956">
<UserControl.Resources> <UserControl.Resources>

View File

@ -5,7 +5,7 @@ using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
namespace Artemis.UI.Shared namespace Artemis.UI.Shared.Controls
{ {
/// <summary> /// <summary>
/// Interaction logic for ColorPicker.xaml /// Interaction logic for ColorPicker.xaml

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

View File

@ -5,7 +5,7 @@ using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
namespace Artemis.UI.Shared namespace Artemis.UI.Shared.Controls
{ {
/// <summary> /// <summary>
/// Interaction logic for DraggableFloat.xaml /// Interaction logic for DraggableFloat.xaml

View File

@ -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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Shared"
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters" xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"> d:DesignHeight="450" d:DesignWidth="800">

View File

@ -8,7 +8,7 @@ using Artemis.Core.Models.Profile;
using Artemis.UI.Shared.Annotations; using Artemis.UI.Shared.Annotations;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services.Interfaces;
namespace Artemis.UI.Shared namespace Artemis.UI.Shared.Controls
{ {
/// <summary> /// <summary>
/// Interaction logic for GradientPicker.xaml /// Interaction logic for GradientPicker.xaml

View File

@ -9,6 +9,7 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:shared="clr-namespace:Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:controls1="clr-namespace:Artemis.UI.Shared.Controls"
mc:Ignorable="d" mc:Ignorable="d"
Title="Gradient Editor" Title="Gradient Editor"
Background="{DynamicResource MaterialDesignPaper}" Background="{DynamicResource MaterialDesignPaper}"
@ -80,7 +81,7 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Label Grid.Column="0" HorizontalAlignment="Right">Color:</Label> <Label Grid.Column="0" HorizontalAlignment="Right">Color:</Label>
<shared:ColorPicker <controls1:ColorPicker
Grid.Row="0" Grid.Row="0"
Grid.Column="1" Grid.Column="1"
x:Name="CurrentColor" x:Name="CurrentColor"

View File

@ -100,6 +100,7 @@ namespace Artemis.UI.Shared.Services.Dialog
{ {
try try
{ {
DialogHost.CloseDialogCommand.Execute(new object(), null);
await ShowDialog<ExceptionDialogViewModel>(arguments); await ShowDialog<ExceptionDialogViewModel>(arguments);
} }
catch (Exception) catch (Exception)

View File

@ -6,12 +6,13 @@
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput" xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput"
xmlns:artemis="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:artemis="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared" xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="25" d:DesignWidth="800" d:DesignHeight="25" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:ColorGradientPropertyInputViewModel}"> d:DataContext="{d:DesignInstance local:ColorGradientPropertyInputViewModel}">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock Margin="0 0 5 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputPrefix}" /> <TextBlock Margin="0 0 5 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputPrefix}" />
<artemis:GradientPicker Width="132" <controls:GradientPicker Width="132"
Margin="0 2" Margin="0 2"
Padding="0 -1" Padding="0 -1"
ColorGradient="{Binding ColorGradientInputValue}" /> ColorGradient="{Binding ColorGradientInputValue}" />

View File

@ -6,12 +6,13 @@
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput" xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:FloatPropertyInputViewModel}"> d:DataContext="{d:DesignInstance local:FloatPropertyInputViewModel}">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock Margin="0 0 5 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputPrefix}" /> <TextBlock Margin="0 0 5 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputPrefix}" />
<shared:DraggableFloat Value="{Binding FloatInputValue}" <controls:DraggableFloat Value="{Binding FloatInputValue}"
StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}" StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}"
DragStarted="{s:Action InputDragStarted}" DragStarted="{s:Action InputDragStarted}"
DragEnded="{s:Action InputDragEnded}" /> DragEnded="{s:Action InputDragEnded}" />

View File

@ -6,12 +6,13 @@
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput" xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:IntPropertyInputViewModel}"> d:DataContext="{d:DesignInstance local:IntPropertyInputViewModel}">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock Margin="0 0 5 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputPrefix}" /> <TextBlock Margin="0 0 5 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputPrefix}" />
<shared:DraggableFloat Value="{Binding IntInputValue}" <controls:DraggableFloat Value="{Binding IntInputValue}"
StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}" StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}"
DragStarted="{s:Action InputDragStarted}" DragStarted="{s:Action InputDragStarted}"
DragEnded="{s:Action InputDragEnded}" /> DragEnded="{s:Action InputDragEnded}" />

View File

@ -6,6 +6,7 @@
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput" xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput"
xmlns:artemis="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:artemis="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared" xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="25" d:DesignWidth="800" d:DesignHeight="25" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:SKColorPropertyInputViewModel}"> d:DataContext="{d:DesignInstance local:SKColorPropertyInputViewModel}">
@ -14,7 +15,7 @@
</UserControl.Resources> </UserControl.Resources>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock Margin="0 0 5 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputPrefix}" /> <TextBlock Margin="0 0 5 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputPrefix}" />
<artemis:ColorPicker Width="132" <controls:ColorPicker Width="132"
Margin="0 -2 0 3" Margin="0 -2 0 3"
Padding="0 -1" Padding="0 -1"
Color="{Binding SKColorInputValue, Converter={StaticResource SKColorToColorConverter}}" /> Color="{Binding SKColorInputValue, Converter={StaticResource SKColorToColorConverter}}" />

View File

@ -6,18 +6,19 @@
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput" xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="25" d:DesignWidth="800" d:DesignHeight="25" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:SKPointPropertyInputViewModel}"> d:DataContext="{d:DesignInstance local:SKPointPropertyInputViewModel}">
<StackPanel Orientation="Horizontal" KeyboardNavigation.IsTabStop="True"> <StackPanel Orientation="Horizontal" KeyboardNavigation.IsTabStop="True">
<TextBlock Margin="0 0 5 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputPrefix}" /> <TextBlock Margin="0 0 5 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputPrefix}" />
<shared:DraggableFloat ToolTip="X-coordinate (horizontal)" <controls:DraggableFloat ToolTip="X-coordinate (horizontal)"
Value="{Binding X}" Value="{Binding X}"
StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}" StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}"
DragStarted="{s:Action InputDragStarted}" DragStarted="{s:Action InputDragStarted}"
DragEnded="{s:Action InputDragEnded}" /> DragEnded="{s:Action InputDragEnded}" />
<TextBlock Margin="5 0" VerticalAlignment="Bottom">,</TextBlock> <TextBlock Margin="5 0" VerticalAlignment="Bottom">,</TextBlock>
<shared:DraggableFloat ToolTip="Y-coordinate (vertical)" <controls:DraggableFloat ToolTip="Y-coordinate (vertical)"
Value="{Binding Y}" Value="{Binding Y}"
StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}" StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}"
DragStarted="{s:Action InputDragStarted}" DragStarted="{s:Action InputDragStarted}"

View File

@ -6,18 +6,19 @@
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput" xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:SKSizePropertyInputViewModel}"> d:DataContext="{d:DesignInstance local:SKSizePropertyInputViewModel}">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBlock Margin="0 0 5 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputPrefix}" /> <TextBlock Margin="0 0 5 4" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputPrefix}" />
<shared:DraggableFloat ToolTip="Height" <controls:DraggableFloat ToolTip="Height"
Value="{Binding Height}" Value="{Binding Height}"
StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}" StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}"
DragStarted="{s:Action InputDragStarted}" DragStarted="{s:Action InputDragStarted}"
DragEnded="{s:Action InputDragEnded}" /> DragEnded="{s:Action InputDragEnded}" />
<TextBlock Margin="5 0" VerticalAlignment="Bottom">,</TextBlock> <TextBlock Margin="5 0" VerticalAlignment="Bottom">,</TextBlock>
<shared:DraggableFloat ToolTip="Width" <controls:DraggableFloat ToolTip="Width"
Value="{Binding Width}" Value="{Binding Width}"
StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}" StepSize="{Binding LayerPropertyViewModel.LayerProperty.InputStepSize}"
DragStarted="{s:Action InputDragStarted}" DragStarted="{s:Action InputDragStarted}"

View File

@ -17,7 +17,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
public static readonly DependencyProperty PixelsPerSecondProperty = DependencyProperty.Register(nameof(PixelsPerSecond), typeof(int), typeof(PropertyTimelineHeader), public static readonly DependencyProperty PixelsPerSecondProperty = DependencyProperty.Register(nameof(PixelsPerSecond), typeof(int), typeof(PropertyTimelineHeader),
new FrameworkPropertyMetadata(default(int), FrameworkPropertyMetadataOptions.AffectsRender)); new FrameworkPropertyMetadata(default(int), FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty HorizontalOffsetProperty = DependencyProperty.Register(nameof(HorizontalOffset), typeof(double), typeof(PropertyTimelineHeader), public static readonly DependencyProperty HorizontalOffsetProperty = DependencyProperty.Register(nameof(HorizontalOffset), typeof(double), typeof(PropertyTimelineHeader),
new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.AffectsRender)); new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.AffectsRender));

View File

@ -16,14 +16,16 @@
<DataTrigger.EnterActions> <DataTrigger.EnterActions>
<BeginStoryboard> <BeginStoryboard>
<Storyboard> <Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Opacity)" To="0.8" Duration="0:0:0.25" /> <DoubleAnimation Storyboard.TargetProperty="(Canvas.Opacity)" To="0.8"
Duration="0:0:0.25" />
</Storyboard> </Storyboard>
</BeginStoryboard> </BeginStoryboard>
</DataTrigger.EnterActions> </DataTrigger.EnterActions>
<DataTrigger.ExitActions> <DataTrigger.ExitActions>
<BeginStoryboard> <BeginStoryboard>
<Storyboard> <Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Opacity)" To="1" Duration="0:0:0.25" /> <DoubleAnimation Storyboard.TargetProperty="(Canvas.Opacity)" To="1"
Duration="0:0:0.25" />
</Storyboard> </Storyboard>
</BeginStoryboard> </BeginStoryboard>
</DataTrigger.ExitActions> </DataTrigger.ExitActions>
@ -33,15 +35,6 @@
</UserControl.Resources> </UserControl.Resources>
<Canvas Style="{StaticResource SelectedStyle}"> <Canvas Style="{StaticResource SelectedStyle}">
<!-- The part of the layer's shape that falls outside the layer -->
<Path Data="{Binding ShapeGeometry, Mode=OneWay}">
<Path.Fill>
<SolidColorBrush Color="{StaticResource Accent700}" Opacity="0.05" />
</Path.Fill>
<Path.Stroke>
<SolidColorBrush Color="{StaticResource Accent700}" Opacity="0.15" />
</Path.Stroke>
</Path>
<!-- The part of the layer's shape that is inside the layer --> <!-- The part of the layer's shape that is inside the layer -->
<Path Data="{Binding ShapeGeometry, Mode=OneWay}"> <Path Data="{Binding ShapeGeometry, Mode=OneWay}">
@ -51,19 +44,18 @@
<Path.Stroke> <Path.Stroke>
<SolidColorBrush Color="{StaticResource Accent700}" /> <SolidColorBrush Color="{StaticResource Accent700}" />
</Path.Stroke> </Path.Stroke>
<Path.OpacityMask>
<VisualBrush Viewport="{Binding ViewportRectangle}" ViewportUnits="Absolute">
<VisualBrush.Visual>
<Path Data="{Binding LayerGeometry, Mode=OneWay}" ClipToBounds="False" Fill="Black" />
</VisualBrush.Visual>
</VisualBrush>
</Path.OpacityMask>
</Path> </Path>
<Path Data="{Binding LayerGeometry, Mode=OneWay}" ClipToBounds="False" StrokeThickness="1.5" StrokeLineJoin="Round" x:Name="LayerPath"> <Rectangle Width="{Binding LayerRect.Width}"
<Path.Stroke> Height="{Binding LayerRect.Height}"
Margin="{Binding LayerRectMargin}"
StrokeThickness="2"
StrokeDashArray="1 1"
StrokeLineJoin="Round"
x:Name="LayerPath">
<Rectangle.Stroke>
<SolidColorBrush Color="{StaticResource Accent400}" /> <SolidColorBrush Color="{StaticResource Accent400}" />
</Path.Stroke> </Rectangle.Stroke>
</Path> </Rectangle>
</Canvas> </Canvas>
</UserControl> </UserControl>

View File

@ -4,12 +4,8 @@ using System.Windows;
using System.Windows.Media; using System.Windows.Media;
using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Profile.LayerShapes; using Artemis.Core.Models.Profile.LayerShapes;
using Artemis.Core.Models.Surface;
using Artemis.UI.Extensions;
using Artemis.UI.Services.Interfaces; using Artemis.UI.Services.Interfaces;
using RGB.NET.Core;
using Stylet; using Stylet;
using Rectangle = Artemis.Core.Models.Profile.LayerShapes.Rectangle;
namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
{ {
@ -32,12 +28,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
} }
public Layer Layer { get; } public Layer Layer { get; }
public Rect LayerRect { get; set; }
public Geometry LayerGeometry { get; set; } public Thickness LayerRectMargin => LayerRect == Rect.Empty ? new Thickness() : new Thickness(LayerRect.Left, LayerRect.Top, 0, 0);
public Geometry OpacityGeometry { get; set; }
public Geometry ShapeGeometry { get; set; }
public Rect ViewportRectangle { get; set; }
public bool IsSelected { get; set; } public bool IsSelected { get; set; }
public Geometry ShapeGeometry { get; set; }
public void Dispose() public void Dispose()
{ {
@ -50,53 +44,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
private void Update() private void Update()
{ {
IsSelected = _profileEditorService.SelectedProfileElement == Layer; IsSelected = _profileEditorService.SelectedProfileElement == Layer;
CreateLayerGeometry(); if (!Layer.Leds.Any() || Layer.LayerShape == null)
LayerRect = Rect.Empty;
else
LayerRect = _layerEditorService.GetLayerBounds(Layer);
CreateShapeGeometry(); CreateShapeGeometry();
CreateViewportRectangle();
}
private void CreateLayerGeometry()
{
if (!Layer.Leds.Any())
{
LayerGeometry = Geometry.Empty;
OpacityGeometry = Geometry.Empty;
ViewportRectangle = Rect.Empty;
return;
}
var group = new GeometryGroup();
group.FillRule = FillRule.Nonzero;
foreach (var led in Layer.Leds)
{
Geometry geometry;
switch (led.RgbLed.Shape)
{
case Shape.Custom:
if (led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad)
geometry = CreateCustomGeometry(led, 2);
else
geometry = CreateCustomGeometry(led, 1);
break;
case Shape.Rectangle:
geometry = CreateRectangleGeometry(led);
break;
case Shape.Circle:
geometry = CreateCircleGeometry(led);
break;
default:
throw new ArgumentOutOfRangeException();
}
group.Children.Add(geometry);
}
var layerGeometry = group.GetOutlinedPathGeometry();
var opacityGeometry = Geometry.Combine(Geometry.Empty, layerGeometry, GeometryCombineMode.Exclude, new TranslateTransform());
LayerGeometry = layerGeometry;
OpacityGeometry = opacityGeometry;
} }
private void CreateShapeGeometry() private void CreateShapeGeometry()
@ -126,59 +79,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
}); });
} }
private void CreateViewportRectangle()
{
if (!Layer.Leds.Any() || Layer.LayerShape == null)
{
ViewportRectangle = Rect.Empty;
return;
}
ViewportRectangle = _layerEditorService.GetLayerBounds(Layer);
}
private Geometry CreateRectangleGeometry(ArtemisLed led)
{
var rect = led.RgbLed.AbsoluteLedRectangle.ToWindowsRect(1);
rect.Inflate(1, 1);
return new RectangleGeometry(rect);
}
private Geometry CreateCircleGeometry(ArtemisLed led)
{
var rect = led.RgbLed.AbsoluteLedRectangle.ToWindowsRect(1);
rect.Inflate(1, 1);
return new EllipseGeometry(rect);
}
private Geometry CreateCustomGeometry(ArtemisLed led, double deflateAmount)
{
var rect = led.RgbLed.AbsoluteLedRectangle.ToWindowsRect(1);
rect.Inflate(1, 1);
try
{
var 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);
}
}
#region Event handlers #region Event handlers
private void LayerOnRenderPropertiesUpdated(object sender, EventArgs e) private void LayerOnRenderPropertiesUpdated(object sender, EventArgs e)
@ -198,8 +98,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
private void ProfileEditorServiceOnProfilePreviewUpdated(object sender, EventArgs e) private void ProfileEditorServiceOnProfilePreviewUpdated(object sender, EventArgs e)
{ {
CreateShapeGeometry(); Update();
CreateViewportRectangle();
} }
#endregion #endregion

View File

@ -7,6 +7,7 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:profileEditor="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.Visualization" xmlns:profileEditor="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.Visualization"
xmlns:utilities="clr-namespace:Artemis.UI.Shared.Utilities;assembly=Artemis.UI.Shared" xmlns:utilities="clr-namespace:Artemis.UI.Shared.Utilities;assembly=Artemis.UI.Shared"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="510.9" d:DesignWidth="800" d:DesignHeight="510.9" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type profileEditor:ProfileViewModel}}"> d:DataContext="{d:DesignInstance {x:Type profileEditor:ProfileViewModel}}">
@ -107,7 +108,7 @@
<TranslateTransform X="{Binding PanZoomViewModel.PanX}" Y="{Binding PanZoomViewModel.PanY}" /> <TranslateTransform X="{Binding PanZoomViewModel.PanX}" Y="{Binding PanZoomViewModel.PanY}" />
</TransformGroup> </TransformGroup>
</Grid.RenderTransform> </Grid.RenderTransform>
<ItemsControl ItemsSource="{Binding DeviceViewModels}"> <ItemsControl ItemsSource="{Binding Devices}">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<Canvas /> <Canvas />
@ -121,7 +122,7 @@
</ItemsControl.ItemContainerStyle> </ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<ContentControl s:View.Model="{Binding}" /> <controls:DeviceVisualizer Device="{Binding}" ShowColors="True"/>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
@ -154,7 +155,6 @@
</ItemsControl> </ItemsControl>
</Grid> </Grid>
<StackPanel ZIndex="1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="10"> <StackPanel ZIndex="1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="10">
<materialDesign:Card Padding="8"> <materialDesign:Card Padding="8">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">

View File

@ -11,6 +11,7 @@ using Artemis.Core.Plugins.Models;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.Core.Services.Storage.Interfaces; using Artemis.Core.Services.Storage.Interfaces;
using Artemis.UI.Events; using Artemis.UI.Events;
using Artemis.UI.Extensions;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools; using Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools;
using Artemis.UI.Screens.Shared; using Artemis.UI.Screens.Shared;
@ -30,7 +31,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
private int _activeToolIndex; private int _activeToolIndex;
private VisualizationToolViewModel _activeToolViewModel; private VisualizationToolViewModel _activeToolViewModel;
private int _previousTool; private int _previousTool;
private TimerUpdateTrigger _updateTrigger;
public ProfileViewModel(IProfileEditorService profileEditorService, public ProfileViewModel(IProfileEditorService profileEditorService,
ILayerEditorService layerEditorService, ILayerEditorService layerEditorService,
@ -47,14 +47,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
Execute.OnUIThreadSync(() => Execute.OnUIThreadSync(() =>
{ {
CanvasViewModels = new ObservableCollection<CanvasViewModel>();
DeviceViewModels = new ObservableCollection<ProfileDeviceViewModel>();
PanZoomViewModel = new PanZoomViewModel {LimitToZero = false}; PanZoomViewModel = new PanZoomViewModel {LimitToZero = false};
CanvasViewModels = new BindableCollection<CanvasViewModel>();
Devices = new BindableCollection<ArtemisDevice>();
DimmedLeds = new BindableCollection<ArtemisLed>();
SelectedLeds = new BindableCollection<ArtemisLed>();
}); });
ApplySurfaceConfiguration(_surfaceService.ActiveSurface); ApplySurfaceConfiguration(_surfaceService.ActiveSurface);
ApplyActiveProfile(); ApplyActiveProfile();
CreateUpdateTrigger();
ActivateToolByIndex(0); ActivateToolByIndex(0);
eventAggregator.Subscribe(this); eventAggregator.Subscribe(this);
@ -62,9 +64,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
public bool IsInitializing { get; private set; } public bool IsInitializing { get; private set; }
public ObservableCollection<CanvasViewModel> CanvasViewModels { get; set; }
public ObservableCollection<ProfileDeviceViewModel> DeviceViewModels { get; set; }
public PanZoomViewModel PanZoomViewModel { get; set; } public PanZoomViewModel PanZoomViewModel { get; set; }
public BindableCollection<CanvasViewModel> CanvasViewModels { get; set; }
public BindableCollection<ArtemisDevice> Devices { get; set; }
public BindableCollection<ArtemisLed> DimmedLeds { get; set; }
public BindableCollection<ArtemisLed> SelectedLeds { get; set; }
public PluginSetting<bool> HighlightSelectedLayer { get; set; } public PluginSetting<bool> HighlightSelectedLayer { get; set; }
public PluginSetting<bool> PauseRenderingOnFocusLoss { get; set; } public PluginSetting<bool> PauseRenderingOnFocusLoss { get; set; }
@ -122,7 +129,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
_profileEditorService.ProfileElementSelected += OnProfileElementSelected; _profileEditorService.ProfileElementSelected += OnProfileElementSelected;
_profileEditorService.SelectedProfileElementUpdated += OnSelectedProfileElementUpdated; _profileEditorService.SelectedProfileElementUpdated += OnSelectedProfileElementUpdated;
_updateTrigger.Start();
base.OnInitialActivate(); base.OnInitialActivate();
} }
@ -137,28 +143,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
HighlightSelectedLayer.Save(); HighlightSelectedLayer.Save();
PauseRenderingOnFocusLoss.Save(); PauseRenderingOnFocusLoss.Save();
try
{
_updateTrigger.Stop();
}
catch (NullReferenceException)
{
// TODO: Remove when fixed in RGB.NET, or avoid double stopping
}
base.OnClose(); base.OnClose();
} }
private void CreateUpdateTrigger()
{
// Borrow RGB.NET's update trigger but limit the FPS
var targetFpsSetting = _settingsService.GetSetting("Core.TargetFrameRate", 25);
var editorTargetFpsSetting = _settingsService.GetSetting("ProfileEditor.TargetFrameRate", 15);
var targetFps = Math.Min(targetFpsSetting.Value, editorTargetFpsSetting.Value);
_updateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / targetFps};
_updateTrigger.Update += UpdateLeds;
}
private void OnActiveSurfaceConfigurationSelected(object sender, SurfaceConfigurationEventArgs e) private void OnActiveSurfaceConfigurationSelected(object sender, SurfaceConfigurationEventArgs e)
{ {
ApplySurfaceConfiguration(e.Surface); ApplySurfaceConfiguration(e.Surface);
@ -193,61 +180,15 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
private void ApplySurfaceConfiguration(ArtemisSurface surface) private void ApplySurfaceConfiguration(ArtemisSurface surface)
{ {
// Make sure all devices have an up-to-date VM Devices.Clear();
Execute.PostToUIThread(() => Devices.AddRange(surface.Devices);
{
lock (DeviceViewModels)
{
var existing = DeviceViewModels.ToList();
var deviceViewModels = new List<ProfileDeviceViewModel>();
// Add missing/update existing
foreach (var surfaceDeviceConfiguration in surface.Devices.OrderBy(d => d.ZIndex).ToList())
{
// Create VMs for missing devices
var viewModel = existing.FirstOrDefault(vm => vm.Device.RgbDevice == surfaceDeviceConfiguration.RgbDevice);
if (viewModel == null)
{
IsInitializing = true;
viewModel = new ProfileDeviceViewModel(surfaceDeviceConfiguration);
}
// Update existing devices
else
viewModel.Device = surfaceDeviceConfiguration;
// Add the viewModel to the list of VMs we want to keep
deviceViewModels.Add(viewModel);
}
DeviceViewModels = new ObservableCollection<ProfileDeviceViewModel>(deviceViewModels);
}
});
}
private void UpdateLeds(object sender, CustomUpdateData customUpdateData)
{
lock (DeviceViewModels)
{
if (IsInitializing)
IsInitializing = DeviceViewModels.Any(d => !d.AddedLeds);
foreach (var profileDeviceViewModel in DeviceViewModels)
profileDeviceViewModel.Update();
}
} }
private void UpdateLedsDimStatus() private void UpdateLedsDimStatus()
{ {
DimmedLeds.Clear();
if (HighlightSelectedLayer.Value && _profileEditorService.SelectedProfileElement is Layer layer) if (HighlightSelectedLayer.Value && _profileEditorService.SelectedProfileElement is Layer layer)
{ DimmedLeds.AddRange(layer.Leds);
foreach (var led in DeviceViewModels.SelectMany(d => d.Leds))
led.IsDimmed = !layer.Leds.Contains(led.Led);
}
else
{
foreach (var led in DeviceViewModels.SelectMany(d => d.Leds))
led.IsDimmed = false;
}
} }
#region Buttons #region Buttons
@ -322,27 +263,27 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
return; return;
layer.ClearLeds(); layer.ClearLeds();
layer.AddLeds(DeviceViewModels.SelectMany(d => d.Leds).Where(vm => vm.IsSelected).Select(vm => vm.Led)); layer.AddLeds(SelectedLeds);
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
} }
public void SelectAll() public void SelectAll()
{ {
foreach (var ledVm in DeviceViewModels.SelectMany(d => d.Leds)) SelectedLeds.Clear();
ledVm.IsSelected = true; SelectedLeds.AddRange(Devices.SelectMany(d => d.Leds));
} }
public void InverseSelection() public void InverseSelection()
{ {
foreach (var ledVm in DeviceViewModels.SelectMany(d => d.Leds)) var current = SelectedLeds.ToList();
ledVm.IsSelected = !ledVm.IsSelected; SelectedLeds.Clear();
SelectedLeds.AddRange(Devices.SelectMany(d => d.Leds).Except(current));
} }
public void ClearSelection() public void ClearSelection()
{ {
foreach (var ledVm in DeviceViewModels.SelectMany(d => d.Leds)) SelectedLeds.Clear();
ledVm.IsSelected = false;
} }
#endregion #endregion
@ -374,20 +315,20 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
public void Handle(MainWindowFocusChangedEvent message) public void Handle(MainWindowFocusChangedEvent message)
{ {
if (PauseRenderingOnFocusLoss == null || ScreenState != ScreenState.Active) // if (PauseRenderingOnFocusLoss == null || ScreenState != ScreenState.Active)
return; // return;
//
try // try
{ // {
if (PauseRenderingOnFocusLoss.Value && !message.IsFocused) // if (PauseRenderingOnFocusLoss.Value && !message.IsFocused)
_updateTrigger.Stop(); // _updateTrigger.Stop();
else if (PauseRenderingOnFocusLoss.Value && message.IsFocused) // else if (PauseRenderingOnFocusLoss.Value && message.IsFocused)
_updateTrigger.Start(); // _updateTrigger.Start();
} // }
catch (NullReferenceException) // catch (NullReferenceException)
{ // {
// TODO: Remove when fixed in RGB.NET, or avoid double stopping // // TODO: Remove when fixed in RGB.NET, or avoid double stopping
} // }
} }
public void Handle(MainWindowKeyEvent message) public void Handle(MainWindowKeyEvent message)
@ -413,5 +354,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
} }
#endregion #endregion
public List<ArtemisLed> GetLedsInRectangle(Rect selectedRect)
{
return Devices.SelectMany(d => d.Leds)
.Where(led => led.RgbLed.AbsoluteLedRectangle.ToWindowsRect(1).IntersectsWith(selectedRect))
.ToList();
}
} }
} }

View File

@ -1,11 +1,8 @@
using System.Collections.Generic; using System.IO;
using System.IO;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Surface;
using Artemis.UI.Extensions;
using Artemis.UI.Properties; using Artemis.UI.Properties;
using Artemis.UI.Services.Interfaces; using Artemis.UI.Services.Interfaces;
@ -31,17 +28,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
var selectedRect = new Rect(MouseDownStartPosition, position); var selectedRect = new Rect(MouseDownStartPosition, position);
// Get selected LEDs // Get selected LEDs
var selectedLeds = new List<ArtemisLed>(); var selectedLeds = ProfileViewModel.GetLedsInRectangle(selectedRect);
foreach (var device in ProfileViewModel.DeviceViewModels) ProfileViewModel.SelectedLeds.Clear();
{ ProfileViewModel.SelectedLeds.AddRange(selectedLeds);
foreach (var ledViewModel in device.Leds)
{
if (ledViewModel.Led.RgbLed.AbsoluteLedRectangle.ToWindowsRect(1).IntersectsWith(selectedRect))
selectedLeds.Add(ledViewModel.Led);
// Unselect everything
ledViewModel.IsSelected = false;
}
}
// Apply the selection to the selected layer layer // Apply the selection to the selected layer layer
if (ProfileEditorService.SelectedProfileElement is Layer layer) if (ProfileEditorService.SelectedProfileElement is Layer layer)
@ -65,17 +54,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
var position = ProfileViewModel.PanZoomViewModel.GetRelativeMousePosition(sender, e); var position = ProfileViewModel.PanZoomViewModel.GetRelativeMousePosition(sender, e);
var selectedRect = new Rect(MouseDownStartPosition, position); var selectedRect = new Rect(MouseDownStartPosition, position);
var selectedLeds = ProfileViewModel.GetLedsInRectangle(selectedRect);
foreach (var device in ProfileViewModel.DeviceViewModels) // Unless shift is held down, clear the current selection
{ if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
foreach (var ledViewModel in device.Leds) ProfileViewModel.SelectedLeds.Clear();
{ ProfileViewModel.SelectedLeds.AddRange(selectedLeds.Except(ProfileViewModel.SelectedLeds));
if (ledViewModel.Led.RgbLed.AbsoluteLedRectangle.ToWindowsRect(1).IntersectsWith(selectedRect))
ledViewModel.IsSelected = true;
else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
ledViewModel.IsSelected = false;
}
}
DragRectangle = selectedRect; DragRectangle = selectedRect;
} }

View File

@ -1,11 +1,8 @@
using System.Collections.Generic; using System.IO;
using System.IO;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Surface;
using Artemis.UI.Extensions;
using Artemis.UI.Properties; using Artemis.UI.Properties;
using Artemis.UI.Services.Interfaces; using Artemis.UI.Services.Interfaces;
@ -31,17 +28,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
var selectedRect = new Rect(MouseDownStartPosition, position); var selectedRect = new Rect(MouseDownStartPosition, position);
// Get selected LEDs // Get selected LEDs
var selectedLeds = new List<ArtemisLed>(); var selectedLeds = ProfileViewModel.GetLedsInRectangle(selectedRect);
foreach (var device in ProfileViewModel.DeviceViewModels)
{
foreach (var ledViewModel in device.Leds)
{
if (ledViewModel.Led.RgbLed.AbsoluteLedRectangle.ToWindowsRect(1).IntersectsWith(selectedRect))
selectedLeds.Add(ledViewModel.Led);
// Unselect everything
ledViewModel.IsSelected = false;
}
}
// Apply the selection to the selected layer layer // Apply the selection to the selected layer layer
if (ProfileEditorService.SelectedProfileElement is Layer layer) if (ProfileEditorService.SelectedProfileElement is Layer layer)
@ -87,17 +74,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools
var position = ProfileViewModel.PanZoomViewModel.GetRelativeMousePosition(sender, e); var position = ProfileViewModel.PanZoomViewModel.GetRelativeMousePosition(sender, e);
var selectedRect = new Rect(MouseDownStartPosition, position); var selectedRect = new Rect(MouseDownStartPosition, position);
var selectedLeds = ProfileViewModel.GetLedsInRectangle(selectedRect);
foreach (var device in ProfileViewModel.DeviceViewModels) // Unless shift is held down, clear the current selection
{ if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
foreach (var ledViewModel in device.Leds) ProfileViewModel.SelectedLeds.Clear();
{ ProfileViewModel.SelectedLeds.AddRange(selectedLeds.Except(ProfileViewModel.SelectedLeds));
if (ledViewModel.Led.RgbLed.AbsoluteLedRectangle.ToWindowsRect(1).IntersectsWith(selectedRect))
ledViewModel.IsSelected = true;
else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
ledViewModel.IsSelected = false;
}
}
DragRectangle = selectedRect; DragRectangle = selectedRect;
} }

View File

@ -6,6 +6,7 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:devices="clr-namespace:Artemis.UI.Screens.Settings.Tabs.Devices" xmlns:devices="clr-namespace:Artemis.UI.Screens.Settings.Tabs.Devices"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
d:DataContext="{d:DesignInstance devices:DeviceSettingsViewModel}" d:DataContext="{d:DesignInstance devices:DeviceSettingsViewModel}"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"> d:DesignHeight="450" d:DesignWidth="800">
@ -29,8 +30,7 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Rectangle Grid.Row="0" Height="140" Width="196" Fill="{DynamicResource MaterialDesignPaper}" /> <Rectangle Grid.Row="0" Height="140" Width="196" Fill="{DynamicResource MaterialDesignPaper}" />
<Image Grid.Row="0" Source="{Binding Device.RgbDevice.DeviceInfo.Image}" Height="130" Width="190" <controls:DeviceVisualizer Device="{Binding Device}" RenderOptions.BitmapScalingMode="HighQuality" Grid.Row="0" Height="130" Width="190" />
RenderOptions.BitmapScalingMode="HighQuality" />
<Button Grid.Row="0" <Button Grid.Row="0"
Style="{StaticResource MaterialDesignFloatingActionMiniButton}" Style="{StaticResource MaterialDesignFloatingActionMiniButton}"
HorizontalAlignment="Right" HorizontalAlignment="Right"

View File

@ -1,6 +1,9 @@
using System.Diagnostics; using System;
using System.Diagnostics;
using System.IO;
using Artemis.Core.Models.Surface; using Artemis.Core.Models.Surface;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Shared.Services.Interfaces;
using Humanizer; using Humanizer;
namespace Artemis.UI.Screens.Settings.Tabs.Devices namespace Artemis.UI.Screens.Settings.Tabs.Devices
@ -8,10 +11,12 @@ namespace Artemis.UI.Screens.Settings.Tabs.Devices
public class DeviceSettingsViewModel public class DeviceSettingsViewModel
{ {
private readonly IDeviceService _deviceService; private readonly IDeviceService _deviceService;
private readonly IDialogService _dialogService;
public DeviceSettingsViewModel(ArtemisDevice device, IDeviceService deviceService) public DeviceSettingsViewModel(ArtemisDevice device, IDeviceService deviceService, IDialogService dialogService)
{ {
_deviceService = deviceService; _deviceService = deviceService;
_dialogService = dialogService;
Device = device; Device = device;
Type = Device.RgbDevice.DeviceInfo.DeviceType.ToString().Humanize(); Type = Device.RgbDevice.DeviceInfo.DeviceType.ToString().Humanize();
@ -36,9 +41,16 @@ namespace Artemis.UI.Screens.Settings.Tabs.Devices
{ {
} }
public void OpenPluginDirectory() public async void OpenPluginDirectory()
{ {
Process.Start(Device.Plugin.PluginInfo.Directory.FullName); try
{
Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Device.Plugin.PluginInfo.Directory.FullName);
}
catch (Exception e)
{
await _dialogService.ShowExceptionDialog("Welp, we couldn't open the device's plugin folder for you", e);
}
} }
} }
} }

View File

@ -7,6 +7,7 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:Artemis.UI.Converters" xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:surfaceEditor="clr-namespace:Artemis.UI.Screens.SurfaceEditor.Visualization" xmlns:surfaceEditor="clr-namespace:Artemis.UI.Screens.SurfaceEditor.Visualization"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
mc:Ignorable="d" mc:Ignorable="d"
d:DataContext="{d:DesignInstance {x:Type surfaceEditor:SurfaceDeviceViewModel}}" d:DataContext="{d:DesignInstance {x:Type surfaceEditor:SurfaceDeviceViewModel}}"
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800"
@ -25,10 +26,7 @@
<RotateTransform Angle="{Binding Device.Rotation}" /> <RotateTransform Angle="{Binding Device.Rotation}" />
</Grid.LayoutTransform> </Grid.LayoutTransform>
<!-- Device image with fallback --> <controls:DeviceVisualizer Device="{Binding Device}"></controls:DeviceVisualizer>
<Image VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Source="{Binding Device.RgbDevice.DeviceInfo.Image, Converter={StaticResource NullToImageConverter}}" />
<Rectangle Fill="{DynamicResource MaterialDesignCardBackground}" <Rectangle Fill="{DynamicResource MaterialDesignCardBackground}"
Stroke="{DynamicResource MaterialDesignTextBoxBorder}" Stroke="{DynamicResource MaterialDesignTextBoxBorder}"
@ -40,26 +38,6 @@
VerticalAlignment="Center" VerticalAlignment="Center"
TextWrapping="Wrap" TextWrapping="Wrap"
TextAlignment="Center" /> TextAlignment="Center" />
<!-- LEDs -->
<ItemsControl ItemsSource="{Binding Leds}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl s:View.Model="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid> </Grid>
<!-- Selection rectangle --> <!-- Selection rectangle -->

View File

@ -9,28 +9,18 @@ namespace Artemis.UI.Screens.SurfaceEditor.Visualization
{ {
public class SurfaceDeviceViewModel : PropertyChangedBase public class SurfaceDeviceViewModel : PropertyChangedBase
{ {
private readonly List<SurfaceLedViewModel> _leds;
private double _dragOffsetX; private double _dragOffsetX;
private double _dragOffsetY; private double _dragOffsetY;
public SurfaceDeviceViewModel(ArtemisDevice device) public SurfaceDeviceViewModel(ArtemisDevice device)
{ {
Device = device; Device = device;
_leds = new List<SurfaceLedViewModel>();
if (Device.RgbDevice != null)
{
foreach (var led in Device.RgbDevice)
_leds.Add(new SurfaceLedViewModel(led));
}
} }
public ArtemisDevice Device { get; set; } public ArtemisDevice Device { get; set; }
public SelectionStatus SelectionStatus { get; set; } public SelectionStatus SelectionStatus { get; set; }
public Cursor Cursor { get; set; } public Cursor Cursor { get; set; }
public IReadOnlyCollection<SurfaceLedViewModel> Leds => _leds.AsReadOnly();
public Rect DeviceRectangle => Device.RgbDevice == null public Rect DeviceRectangle => Device.RgbDevice == null
? new Rect() ? new Rect()
: new Rect(Device.X, Device.Y, Device.RgbDevice.DeviceRectangle.Size.Width, Device.RgbDevice.DeviceRectangle.Size.Height); : new Rect(Device.X, Device.Y, Device.RgbDevice.DeviceRectangle.Size.Width, Device.RgbDevice.DeviceRectangle.Size.Height);

View File

@ -1,21 +0,0 @@
<UserControl
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:Converters="clr-namespace:Artemis.UI.Converters"
xmlns:surfaceEditor="clr-namespace:Artemis.UI.Screens.SurfaceEditor.Visualization"
x:Class="Artemis.UI.Screens.SurfaceEditor.Visualization.SurfaceLedView"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance {x:Type surfaceEditor:SurfaceLedViewModel}}"
d:DesignHeight="25" d:DesignWidth="25">
<UserControl.Resources>
<Converters:NullToImageConverter x:Key="NullToImageConverter" />
</UserControl.Resources>
<Grid Width="{Binding Width}" Height="{Binding Height}">
<Grid.Background>
<ImageBrush AlignmentX="Center" AlignmentY="Center" Stretch="Fill"
ImageSource="{Binding Led.Image, Converter={StaticResource NullToImageConverter}}" />
</Grid.Background>
</Grid>
</UserControl>

View File

@ -1,38 +0,0 @@
using System.ComponentModel;
using RGB.NET.Core;
using Stylet;
namespace Artemis.UI.Screens.SurfaceEditor.Visualization
{
public class SurfaceLedViewModel : PropertyChangedBase
{
public SurfaceLedViewModel(Led led)
{
Led = led;
ApplyLedToViewModel();
Led.PropertyChanged += ApplyViewModelOnLedChange;
}
public Led Led { get; set; }
public double X { get; set; }
public double Y { get; set; }
public double Width { get; set; }
public double Height { get; set; }
public void ApplyLedToViewModel()
{
// Don't want ActualLocation here since rotation is done in XAML
X = Led.Location.X * Led.Device.Scale.Horizontal;
Y = Led.Location.Y * Led.Device.Scale.Vertical;
Width = Led.ActualSize.Width;
Height = Led.ActualSize.Height;
}
private void ApplyViewModelOnLedChange(object sender, PropertyChangedEventArgs args)
{
if (args.PropertyName == "Location" || args.PropertyName == "ActualSize") ApplyLedToViewModel();
}
}
}

View File

@ -7,6 +7,7 @@ using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services.Interfaces;
using Ninject; using Ninject;
using Stylet; using Stylet;
using GradientPicker = Artemis.UI.Shared.Controls.GradientPicker;
namespace Artemis.UI.Screens namespace Artemis.UI.Screens
{ {