diff --git a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj index 60cb67ff1..a958d4de9 100644 --- a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj +++ b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj @@ -26,10 +26,12 @@ + + @@ -50,6 +52,11 @@ false + + + ..\..\..\RGB.NET\bin\netstandard2.0\RGB.NET.Core.dll + + diff --git a/src/Artemis.UI.Shared/ColorPicker.xaml b/src/Artemis.UI.Shared/Controls/ColorPicker.xaml similarity index 99% rename from src/Artemis.UI.Shared/ColorPicker.xaml rename to src/Artemis.UI.Shared/Controls/ColorPicker.xaml index 09c3fb48c..4d6db87f3 100644 --- a/src/Artemis.UI.Shared/ColorPicker.xaml +++ b/src/Artemis.UI.Shared/Controls/ColorPicker.xaml @@ -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"> diff --git a/src/Artemis.UI.Shared/ColorPicker.xaml.cs b/src/Artemis.UI.Shared/Controls/ColorPicker.xaml.cs similarity index 99% rename from src/Artemis.UI.Shared/ColorPicker.xaml.cs rename to src/Artemis.UI.Shared/Controls/ColorPicker.xaml.cs index 0def96395..3adfea0e4 100644 --- a/src/Artemis.UI.Shared/ColorPicker.xaml.cs +++ b/src/Artemis.UI.Shared/Controls/ColorPicker.xaml.cs @@ -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 { /// /// Interaction logic for ColorPicker.xaml diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs new file mode 100644 index 000000000..0a19bf490 --- /dev/null +++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs @@ -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 _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(); + } + + + 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); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs new file mode 100644 index 000000000..68274afad --- /dev/null +++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/DraggableFloat.xaml b/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml similarity index 97% rename from src/Artemis.UI.Shared/DraggableFloat.xaml rename to src/Artemis.UI.Shared/Controls/DraggableFloat.xaml index 6014be870..1da088f62 100644 --- a/src/Artemis.UI.Shared/DraggableFloat.xaml +++ b/src/Artemis.UI.Shared/Controls/DraggableFloat.xaml @@ -1,4 +1,4 @@ - /// Interaction logic for DraggableFloat.xaml diff --git a/src/Artemis.UI.Shared/GradientPicker.xaml b/src/Artemis.UI.Shared/Controls/GradientPicker.xaml similarity index 95% rename from src/Artemis.UI.Shared/GradientPicker.xaml rename to src/Artemis.UI.Shared/Controls/GradientPicker.xaml index a2de5e8bd..9c74ca645 100644 --- a/src/Artemis.UI.Shared/GradientPicker.xaml +++ b/src/Artemis.UI.Shared/Controls/GradientPicker.xaml @@ -1,9 +1,8 @@ - diff --git a/src/Artemis.UI.Shared/GradientPicker.xaml.cs b/src/Artemis.UI.Shared/Controls/GradientPicker.xaml.cs similarity index 98% rename from src/Artemis.UI.Shared/GradientPicker.xaml.cs rename to src/Artemis.UI.Shared/Controls/GradientPicker.xaml.cs index e41df9eb5..8e740ebac 100644 --- a/src/Artemis.UI.Shared/GradientPicker.xaml.cs +++ b/src/Artemis.UI.Shared/Controls/GradientPicker.xaml.cs @@ -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 { /// /// Interaction logic for GradientPicker.xaml diff --git a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorView.xaml b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorView.xaml index 8bf368fc2..82881cf69 100644 --- a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorView.xaml +++ b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorView.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 @@ - (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; } } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/ColorGradientPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/ColorGradientPropertyInputView.xaml index 878c16276..d50535da3 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/ColorGradientPropertyInputView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/ColorGradientPropertyInputView.xaml @@ -6,12 +6,13 @@ xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput" xmlns:artemis="clr-namespace:Artemis.UI.Shared;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" d:DesignHeight="25" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:ColorGradientPropertyInputViewModel}"> - diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/FloatPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/FloatPropertyInputView.xaml index bb8514f2e..3a718dfcb 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/FloatPropertyInputView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/FloatPropertyInputView.xaml @@ -6,12 +6,13 @@ xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput" xmlns:s="https://github.com/canton7/Stylet" 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" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:FloatPropertyInputViewModel}"> - diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/IntPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/IntPropertyInputView.xaml index 94b47cbb0..cf2997f4a 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/IntPropertyInputView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/IntPropertyInputView.xaml @@ -6,12 +6,13 @@ xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput" xmlns:s="https://github.com/canton7/Stylet" 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" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:IntPropertyInputViewModel}"> - diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKColorPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKColorPropertyInputView.xaml index 9c150443b..524991b5e 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKColorPropertyInputView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKColorPropertyInputView.xaml @@ -6,6 +6,7 @@ xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput" xmlns:artemis="clr-namespace:Artemis.UI.Shared;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" d:DesignHeight="25" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:SKColorPropertyInputViewModel}"> @@ -14,7 +15,7 @@ - diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKPointPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKPointPropertyInputView.xaml index 057d2e5a3..bf0243f20 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKPointPropertyInputView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKPointPropertyInputView.xaml @@ -6,18 +6,19 @@ xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput" xmlns:s="https://github.com/canton7/Stylet" 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" d:DesignHeight="25" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:SKPointPropertyInputViewModel}"> - , - - , - - + - + @@ -33,15 +35,6 @@ - - - - - - - - - @@ -51,19 +44,18 @@ - - - - - - - - - + + - - + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs index 4def7f477..80402ff5e 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs @@ -4,12 +4,8 @@ using System.Windows; using System.Windows.Media; using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile.LayerShapes; -using Artemis.Core.Models.Surface; -using Artemis.UI.Extensions; using Artemis.UI.Services.Interfaces; -using RGB.NET.Core; using Stylet; -using Rectangle = Artemis.Core.Models.Profile.LayerShapes.Rectangle; namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization { @@ -32,12 +28,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization } public Layer Layer { get; } - - public Geometry LayerGeometry { get; set; } - public Geometry OpacityGeometry { get; set; } - public Geometry ShapeGeometry { get; set; } - public Rect ViewportRectangle { get; set; } + public Rect LayerRect { get; set; } + public Thickness LayerRectMargin => LayerRect == Rect.Empty ? new Thickness() : new Thickness(LayerRect.Left, LayerRect.Top, 0, 0); public bool IsSelected { get; set; } + public Geometry ShapeGeometry { get; set; } public void Dispose() { @@ -50,53 +44,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization private void Update() { IsSelected = _profileEditorService.SelectedProfileElement == Layer; - CreateLayerGeometry(); + if (!Layer.Leds.Any() || Layer.LayerShape == null) + LayerRect = Rect.Empty; + else + LayerRect = _layerEditorService.GetLayerBounds(Layer); + 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() @@ -126,60 +79,7 @@ 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) { @@ -198,8 +98,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization private void ProfileEditorServiceOnProfilePreviewUpdated(object sender, EventArgs e) { - CreateShapeGeometry(); - CreateViewportRectangle(); + Update(); } #endregion diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileView.xaml index a336795c2..e14c5feca 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileView.xaml @@ -7,6 +7,7 @@ xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:profileEditor="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.Visualization" 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" d:DesignHeight="510.9" d:DesignWidth="800" d:DataContext="{d:DesignInstance {x:Type profileEditor:ProfileViewModel}}"> @@ -107,7 +108,7 @@ - + @@ -121,7 +122,7 @@ - + @@ -154,7 +155,6 @@ - diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs index 1378cf96c..7121a7eec 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs @@ -11,6 +11,7 @@ using Artemis.Core.Plugins.Models; using Artemis.Core.Services; using Artemis.Core.Services.Storage.Interfaces; using Artemis.UI.Events; +using Artemis.UI.Extensions; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools; using Artemis.UI.Screens.Shared; @@ -30,7 +31,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization private int _activeToolIndex; private VisualizationToolViewModel _activeToolViewModel; private int _previousTool; - private TimerUpdateTrigger _updateTrigger; public ProfileViewModel(IProfileEditorService profileEditorService, ILayerEditorService layerEditorService, @@ -47,14 +47,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization Execute.OnUIThreadSync(() => { - CanvasViewModels = new ObservableCollection(); - DeviceViewModels = new ObservableCollection(); PanZoomViewModel = new PanZoomViewModel {LimitToZero = false}; + + CanvasViewModels = new BindableCollection(); + Devices = new BindableCollection(); + DimmedLeds = new BindableCollection(); + SelectedLeds = new BindableCollection(); }); ApplySurfaceConfiguration(_surfaceService.ActiveSurface); ApplyActiveProfile(); - CreateUpdateTrigger(); ActivateToolByIndex(0); eventAggregator.Subscribe(this); @@ -62,9 +64,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization public bool IsInitializing { get; private set; } - public ObservableCollection CanvasViewModels { get; set; } - public ObservableCollection DeviceViewModels { get; set; } + public PanZoomViewModel PanZoomViewModel { get; set; } + + public BindableCollection CanvasViewModels { get; set; } + public BindableCollection Devices { get; set; } + public BindableCollection DimmedLeds { get; set; } + public BindableCollection SelectedLeds { get; set; } + public PluginSetting HighlightSelectedLayer { get; set; } public PluginSetting PauseRenderingOnFocusLoss { get; set; } @@ -122,7 +129,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization _profileEditorService.ProfileElementSelected += OnProfileElementSelected; _profileEditorService.SelectedProfileElementUpdated += OnSelectedProfileElementUpdated; - _updateTrigger.Start(); base.OnInitialActivate(); } @@ -137,28 +143,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization HighlightSelectedLayer.Save(); PauseRenderingOnFocusLoss.Save(); - try - { - _updateTrigger.Stop(); - } - catch (NullReferenceException) - { - // TODO: Remove when fixed in RGB.NET, or avoid double stopping - } - 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) { ApplySurfaceConfiguration(e.Surface); @@ -193,61 +180,15 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization private void ApplySurfaceConfiguration(ArtemisSurface surface) { - // Make sure all devices have an up-to-date VM - Execute.PostToUIThread(() => - { - lock (DeviceViewModels) - { - var existing = DeviceViewModels.ToList(); - var deviceViewModels = new List(); - - // 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(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(); - } + Devices.Clear(); + Devices.AddRange(surface.Devices); } private void UpdateLedsDimStatus() { + DimmedLeds.Clear(); if (HighlightSelectedLayer.Value && _profileEditorService.SelectedProfileElement is Layer layer) - { - 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; - } + DimmedLeds.AddRange(layer.Leds); } #region Buttons @@ -322,27 +263,27 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization return; layer.ClearLeds(); - layer.AddLeds(DeviceViewModels.SelectMany(d => d.Leds).Where(vm => vm.IsSelected).Select(vm => vm.Led)); + layer.AddLeds(SelectedLeds); _profileEditorService.UpdateSelectedProfileElement(); } public void SelectAll() { - foreach (var ledVm in DeviceViewModels.SelectMany(d => d.Leds)) - ledVm.IsSelected = true; + SelectedLeds.Clear(); + SelectedLeds.AddRange(Devices.SelectMany(d => d.Leds)); } public void InverseSelection() { - foreach (var ledVm in DeviceViewModels.SelectMany(d => d.Leds)) - ledVm.IsSelected = !ledVm.IsSelected; + var current = SelectedLeds.ToList(); + SelectedLeds.Clear(); + SelectedLeds.AddRange(Devices.SelectMany(d => d.Leds).Except(current)); } public void ClearSelection() { - foreach (var ledVm in DeviceViewModels.SelectMany(d => d.Leds)) - ledVm.IsSelected = false; + SelectedLeds.Clear(); } #endregion @@ -374,20 +315,20 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization public void Handle(MainWindowFocusChangedEvent message) { - if (PauseRenderingOnFocusLoss == null || ScreenState != ScreenState.Active) - return; - - try - { - if (PauseRenderingOnFocusLoss.Value && !message.IsFocused) - _updateTrigger.Stop(); - else if (PauseRenderingOnFocusLoss.Value && message.IsFocused) - _updateTrigger.Start(); - } - catch (NullReferenceException) - { - // TODO: Remove when fixed in RGB.NET, or avoid double stopping - } + // if (PauseRenderingOnFocusLoss == null || ScreenState != ScreenState.Active) + // return; + // + // try + // { + // if (PauseRenderingOnFocusLoss.Value && !message.IsFocused) + // _updateTrigger.Stop(); + // else if (PauseRenderingOnFocusLoss.Value && message.IsFocused) + // _updateTrigger.Start(); + // } + // catch (NullReferenceException) + // { + // // TODO: Remove when fixed in RGB.NET, or avoid double stopping + // } } public void Handle(MainWindowKeyEvent message) @@ -413,5 +354,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization } #endregion + + public List GetLedsInRectangle(Rect selectedRect) + { + return Devices.SelectMany(d => d.Leds) + .Where(led => led.RgbLed.AbsoluteLedRectangle.ToWindowsRect(1).IntersectsWith(selectedRect)) + .ToList(); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionRemoveToolViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionRemoveToolViewModel.cs index 414f63f87..75570f727 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionRemoveToolViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionRemoveToolViewModel.cs @@ -1,11 +1,8 @@ -using System.Collections.Generic; -using System.IO; +using System.IO; using System.Linq; using System.Windows; using System.Windows.Input; using Artemis.Core.Models.Profile; -using Artemis.Core.Models.Surface; -using Artemis.UI.Extensions; using Artemis.UI.Properties; using Artemis.UI.Services.Interfaces; @@ -31,17 +28,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools var selectedRect = new Rect(MouseDownStartPosition, position); // Get selected LEDs - var selectedLeds = new List(); - 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; - } - } + var selectedLeds = ProfileViewModel.GetLedsInRectangle(selectedRect); + ProfileViewModel.SelectedLeds.Clear(); + ProfileViewModel.SelectedLeds.AddRange(selectedLeds); // Apply the selection to the selected 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 selectedRect = new Rect(MouseDownStartPosition, position); + 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)) - ledViewModel.IsSelected = true; - else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift)) - ledViewModel.IsSelected = false; - } - } + // Unless shift is held down, clear the current selection + if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift)) + ProfileViewModel.SelectedLeds.Clear(); + ProfileViewModel.SelectedLeds.AddRange(selectedLeds.Except(ProfileViewModel.SelectedLeds)); DragRectangle = selectedRect; } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs index 3a6661113..df928ce6b 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs @@ -1,11 +1,8 @@ -using System.Collections.Generic; -using System.IO; +using System.IO; using System.Linq; using System.Windows; using System.Windows.Input; using Artemis.Core.Models.Profile; -using Artemis.Core.Models.Surface; -using Artemis.UI.Extensions; using Artemis.UI.Properties; using Artemis.UI.Services.Interfaces; @@ -31,17 +28,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools var selectedRect = new Rect(MouseDownStartPosition, position); // Get selected LEDs - var selectedLeds = new List(); - 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; - } - } + var selectedLeds = ProfileViewModel.GetLedsInRectangle(selectedRect); // Apply the selection to the selected 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 selectedRect = new Rect(MouseDownStartPosition, position); + 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)) - ledViewModel.IsSelected = true; - else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift)) - ledViewModel.IsSelected = false; - } - } + // Unless shift is held down, clear the current selection + if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift)) + ProfileViewModel.SelectedLeds.Clear(); + ProfileViewModel.SelectedLeds.AddRange(selectedLeds.Except(ProfileViewModel.SelectedLeds)); DragRectangle = selectedRect; } diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsView.xaml index 45f33c9f0..dca39d554 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsView.xaml @@ -6,6 +6,7 @@ xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:s="https://github.com/canton7/Stylet" 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}" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> @@ -29,8 +30,7 @@ - +