diff --git a/src/Artemis.Core/Artemis.Core.csproj.DotSettings b/src/Artemis.Core/Artemis.Core.csproj.DotSettings
index b1ea041c1..7d6c52363 100644
--- a/src/Artemis.Core/Artemis.Core.csproj.DotSettings
+++ b/src/Artemis.Core/Artemis.Core.csproj.DotSettings
@@ -43,6 +43,7 @@
True
True
True
+ True
True
True
True
diff --git a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs
index 0317d99fa..e9efb8a36 100644
--- a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs
+++ b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs
@@ -32,7 +32,7 @@ namespace Artemis.Core
GreenScale = 1;
BlueScale = 1;
IsEnabled = true;
-
+
InputIdentifiers = new List();
Leds = rgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly();
@@ -49,7 +49,7 @@ namespace Artemis.Core
RgbDevice = rgbDevice;
DeviceProvider = deviceProvider;
Surface = surface;
-
+
InputIdentifiers = new List();
foreach (DeviceInputIdentifierEntity identifierEntity in DeviceEntity.InputIdentifiers)
InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(identifierEntity.InputProvider, identifierEntity.Identifier));
@@ -95,13 +95,13 @@ namespace Artemis.Core
///
/// Gets a read only collection containing the LEDs of this device
///
- public ReadOnlyCollection Leds { get; }
+ public ReadOnlyCollection Leds { get; private set; }
///
/// Gets a dictionary containing all the LEDs of this device with their corresponding RGB.NET as
/// key
///
- public ReadOnlyDictionary LedIds { get; }
+ public ReadOnlyDictionary LedIds { get; private set; }
///
/// Gets a list of input identifiers associated with this device
@@ -225,6 +225,34 @@ namespace Artemis.Core
}
}
+ ///
+ /// Gets or sets the physical layout of the device e.g. ISO or ANSI.
+ /// Only applicable to keyboards
+ ///
+ public string? PhysicalLayout
+ {
+ get => DeviceEntity.PhysicalLayout;
+ set
+ {
+ DeviceEntity.PhysicalLayout = value;
+ OnPropertyChanged(nameof(PhysicalLayout));
+ }
+ }
+
+ ///
+ /// Gets or sets the logical layout of the device e.g. DE, UK or US.
+ /// Only applicable to keyboards
+ ///
+ public string? LogicalLayout
+ {
+ get => DeviceEntity.LogicalLayout;
+ set
+ {
+ DeviceEntity.LogicalLayout = value;
+ OnPropertyChanged(nameof(LogicalLayout));
+ }
+ }
+
///
/// Gets the layout of the device expanded with Artemis-specific data
///
@@ -259,13 +287,58 @@ namespace Artemis.Core
return artemisLed;
}
+ ///
+ /// Generates the default layout file name of the device
+ ///
+ /// If true, the .xml extension is added to the file name
+ /// The resulting file name e.g. CORSAIR GLAIVE.xml or K95 RGB-ISO.xml
+ public string GetLayoutFileName(bool includeExtension = true)
+ {
+ // Take out invalid file name chars, may not be perfect but neither are you
+ string fileName = System.IO.Path.GetInvalidFileNameChars().Aggregate(RgbDevice.DeviceInfo.Model, (current, c) => current.Replace(c, '-'));
+ if (PhysicalLayout != null)
+ fileName = $"{fileName}-{PhysicalLayout.ToUpper()}";
+ if (includeExtension)
+ fileName = $"{fileName}.xml";
+
+ return fileName;
+ }
+
+ ///
+ /// Loads the best layout for this device, preferring user layouts and falling back to default layouts
+ ///
public void LoadBestLayout()
{
- ArtemisLayout artemisLayout = DeviceProvider.LoadLayout(RgbDevice);
- if (artemisLayout.IsValid)
- artemisLayout.DeviceLayout!.ApplyTo(RgbDevice);
+ // If supported, detect the device layout so that we can load the correct one
+ if (DeviceProvider.CanDetectLogicalLayout || DeviceProvider.CanDetectPhysicalLayout && RgbDevice is IKeyboard)
+ DeviceProvider.DetectDeviceLayout(this);
- Layout = artemisLayout;
+ // Look for a user layout
+ // ... here
+
+ // Try loading a device provider layout, if that comes back valid we use that
+ ArtemisLayout deviceProviderLayout = DeviceProvider.LoadLayout(this);
+
+ // Finally fall back to a default layout
+ // .. do it!
+
+ ApplyLayout(deviceProviderLayout);
+ }
+
+ ///
+ /// Applies the provided layout to the device
+ ///
+ /// The layout to apply
+ public void ApplyLayout(ArtemisLayout layout)
+ {
+ if (layout.IsValid)
+ layout.RgbLayout!.ApplyTo(RgbDevice);
+
+ Layout = layout;
+ Leds = RgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly();
+ LedIds = new ReadOnlyDictionary(Leds.ToDictionary(l => l.RgbLed.Id, l => l));
+ Layout.ApplyDevice(this);
+ Leds = Layout.Leds.Select(l => l.Led);
}
internal void ApplyToEntity()
diff --git a/src/Artemis.Core/Models/Surface/ArtemisLayout.cs b/src/Artemis.Core/Models/Surface/ArtemisLayout.cs
deleted file mode 100644
index cf61d8752..000000000
--- a/src/Artemis.Core/Models/Surface/ArtemisLayout.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using System;
-using RGB.NET.Layout;
-
-namespace Artemis.Core
-{
- ///
- /// Represents a device layout decorated with extra Artemis-specific data
- ///
- public class ArtemisLayout
- {
- ///
- /// Creates a new instance of the class
- ///
- /// The path of the layout XML file
- public ArtemisLayout(string filePath)
- {
- FilePath = filePath;
- DeviceLayout = DeviceLayout.Load(FilePath);
- IsValid = DeviceLayout != null;
- }
-
- ///
- /// Gets the file path the layout was (attempted to be) loaded from
- ///
- public string FilePath { get; }
-
- ///
- /// Gets the RGB.NET device layout
- ///
- public DeviceLayout? DeviceLayout { get; }
-
- ///
- /// Gets a boolean indicating whether a valid layout was loaded
- ///
- public bool IsValid { get; }
-
- ///
- /// Gets the device this image is applied to
- ///
- public ArtemisDevice? Device { get; internal set; }
-
- ///
- /// Gets or sets the physical layout of the device. Only applicable to keyboards
- ///
- public string? PhysicalLayout { get; set; }
-
- ///
- /// Gets or sets the logical layout of the device. Only applicable to keyboards
- ///
- public string? LogicalLayout { get; set; }
-
- ///
- /// Gets or sets the image of the device
- ///
- public Uri? Image { get; set; }
- }
-}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Surface/ArtemisLed.cs b/src/Artemis.Core/Models/Surface/ArtemisLed.cs
index cd946fd6d..4122b870d 100644
--- a/src/Artemis.Core/Models/Surface/ArtemisLed.cs
+++ b/src/Artemis.Core/Models/Surface/ArtemisLed.cs
@@ -1,4 +1,5 @@
-using RGB.NET.Core;
+using System.Linq;
+using RGB.NET.Core;
using SkiaSharp;
namespace Artemis.Core
@@ -16,6 +17,9 @@ namespace Artemis.Core
RgbLed = led;
Device = device;
CalculateRectangles();
+
+ Layout = Device.Layout?.Leds.FirstOrDefault(l => l.RgbLayout.Id == led.Id.ToString());
+
}
///
@@ -46,7 +50,7 @@ namespace Artemis.Core
private set => SetAndNotify(ref _absoluteRectangle, value);
}
- public ArtemisLedLayout? Layout { get; set; }
+ public ArtemisLedLayout? Layout { get; }
internal void CalculateRectangles()
{
diff --git a/src/Artemis.Core/Models/Surface/ArtemisLedLayout.cs b/src/Artemis.Core/Models/Surface/ArtemisLedLayout.cs
deleted file mode 100644
index 884e0b226..000000000
--- a/src/Artemis.Core/Models/Surface/ArtemisLedLayout.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using System;
-
-namespace Artemis.Core
-{
- public class ArtemisLedLayout
- {
- public ArtemisLed Led { get; set; }
- public Uri Image { get; set; }
- }
-}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Surface/Layout/ArtemisLayout.cs b/src/Artemis.Core/Models/Surface/Layout/ArtemisLayout.cs
new file mode 100644
index 000000000..29553b14e
--- /dev/null
+++ b/src/Artemis.Core/Models/Surface/Layout/ArtemisLayout.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using RGB.NET.Layout;
+
+namespace Artemis.Core
+{
+ ///
+ /// Represents a device layout decorated with extra Artemis-specific data
+ ///
+ public class ArtemisLayout
+ {
+ ///
+ /// Creates a new instance of the class
+ ///
+ /// The path of the layout XML file
+ public ArtemisLayout(string filePath)
+ {
+ FilePath = filePath;
+ Leds = new List();
+
+ DeviceLayout? deviceLayout = DeviceLayout.Load(FilePath, typeof(LayoutCustomDeviceData), typeof(LayoutCustomLedData));
+ if (deviceLayout != null)
+ {
+ RgbLayout = deviceLayout;
+ IsValid = true;
+ }
+ else
+ {
+ RgbLayout = new DeviceLayout();
+ IsValid = false;
+ }
+
+ if (IsValid)
+ Leds.AddRange(RgbLayout.Leds.Select(l => new ArtemisLedLayout(this, l)));
+
+ LayoutCustomDeviceData = (LayoutCustomDeviceData?) RgbLayout.CustomData ?? new LayoutCustomDeviceData();
+ ApplyCustomDeviceData();
+ }
+
+ ///
+ /// Gets the file path the layout was (attempted to be) loaded from
+ ///
+ public string FilePath { get; }
+
+ ///
+ /// Gets the RGB.NET device layout
+ ///
+ public DeviceLayout RgbLayout { get; }
+
+ ///
+ /// Gets the device this layout is applied to
+ ///
+ public ArtemisDevice? Device { get; private set; }
+
+ ///
+ /// Gets a boolean indicating whether a valid layout was loaded
+ ///
+ public bool IsValid { get; }
+
+ ///
+ /// Gets or sets the image of the device
+ ///
+ public Uri? Image { get; set; }
+
+ public List Leds { get; }
+
+ internal LayoutCustomDeviceData LayoutCustomDeviceData { get; set; }
+
+ internal void ApplyDevice(ArtemisDevice artemisDevice)
+ {
+ Device = artemisDevice;
+ foreach (ArtemisLedLayout artemisLedLayout in Leds)
+ artemisLedLayout.ApplyDevice(Device);
+ }
+
+ private void ApplyCustomDeviceData()
+ {
+ Uri layoutDirectory = new(Path.GetDirectoryName(FilePath)! + "\\", UriKind.Absolute);
+ if (LayoutCustomDeviceData.DeviceImage != null)
+ Image = new Uri(layoutDirectory, new Uri(LayoutCustomDeviceData.DeviceImage, UriKind.Relative));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Surface/Layout/ArtemisLedLayout.cs b/src/Artemis.Core/Models/Surface/Layout/ArtemisLedLayout.cs
new file mode 100644
index 000000000..ab41cb1d5
--- /dev/null
+++ b/src/Artemis.Core/Models/Surface/Layout/ArtemisLedLayout.cs
@@ -0,0 +1,55 @@
+using System;
+using System.IO;
+using System.Linq;
+using RGB.NET.Layout;
+
+namespace Artemis.Core
+{
+ public class ArtemisLedLayout
+ {
+ internal ArtemisLedLayout(ArtemisLayout layout, ILedLayout led)
+ {
+ Layout = layout;
+ RgbLayout = led;
+ LayoutCustomLedData = (LayoutCustomLedData?) led.CustomData ?? new LayoutCustomLedData();
+ }
+
+ public ArtemisLayout Layout { get; }
+
+ ///
+ /// Gets the RGB.NET LED Layout of this Artemis LED layout
+ ///
+ public ILedLayout RgbLayout { get; }
+
+ ///
+ /// Gets the LED this layout is applied to
+ ///
+ public ArtemisLed? Led { get; protected set; }
+
+ ///
+ /// Gets the name of the logical layout this LED belongs to
+ ///
+ public string? LogicalName { get; private set; }
+
+ ///
+ /// Gets the image of the LED
+ ///
+ public Uri? Image { get; private set; }
+
+ internal LayoutCustomLedData LayoutCustomLedData { get; set; }
+
+ public void ApplyDevice(ArtemisDevice device)
+ {
+ Led = device.Leds.FirstOrDefault(d => d.RgbLed.Id.ToString() == RgbLayout.Id);
+ ApplyCustomLedData();
+ }
+
+ private void ApplyCustomLedData()
+ {
+ if (Led == null)
+ return;
+
+ Uri layoutDirectory = new(Path.GetDirectoryName(Layout.FilePath)! + "\\", UriKind.Absolute);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Surface/Layout/LayoutCustomDeviceData.cs b/src/Artemis.Core/Models/Surface/Layout/LayoutCustomDeviceData.cs
new file mode 100644
index 000000000..56144ca56
--- /dev/null
+++ b/src/Artemis.Core/Models/Surface/Layout/LayoutCustomDeviceData.cs
@@ -0,0 +1,15 @@
+using System.Xml.Serialization;
+#pragma warning disable 1591
+
+namespace Artemis.Core
+{
+ ///
+ /// Represents extra Artemis-specific information stored in RGB.NET layouts
+ ///
+ [XmlRoot("CustomData")]
+ public class LayoutCustomDeviceData
+ {
+ [XmlElement("DeviceImage")]
+ public string? DeviceImage { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Surface/Layout/LayoutCustomLedData.cs b/src/Artemis.Core/Models/Surface/Layout/LayoutCustomLedData.cs
new file mode 100644
index 000000000..62e029e53
--- /dev/null
+++ b/src/Artemis.Core/Models/Surface/Layout/LayoutCustomLedData.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+using System.Xml.Serialization;
+#pragma warning disable 1591
+
+namespace Artemis.Core
+{
+ ///
+ /// Represents extra Artemis-specific information stored in RGB.NET layouts
+ ///
+ [XmlRoot("CustomData")]
+ public class LayoutCustomLedData
+ {
+ [XmlArray("LogicalLayouts")]
+ public List? LogicalLayouts { get; set; }
+ }
+
+ ///
+ /// Represents extra Artemis-specific information stored in RGB.NET layouts
+ ///
+ [XmlType("LogicalLayout")]
+ public class LayoutCustomLedDataLogicalLayout
+ {
+ [XmlAttribute("Name")]
+ public string? Name { get; set; }
+
+ [XmlAttribute("Image")]
+ public string? Image { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs b/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs
index 9ae9f0e20..d069d821a 100644
--- a/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs
+++ b/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs
@@ -1,6 +1,5 @@
using System;
using System.IO;
-using System.Linq;
using Ninject;
using RGB.NET.Core;
using Serilog;
@@ -35,12 +34,20 @@ namespace Artemis.Core.DeviceProviders
public ILogger? Logger { get; set; }
///
- /// A boolean indicating whether this device provider detects the physical layout of connected keyboards
+ /// A boolean indicating whether this device provider detects the physical layout of connected keyboards.
+ ///
+ /// Note: is only called when this or
+ /// is .
+ ///
///
public bool CanDetectPhysicalLayout { get; protected set; }
///
/// A boolean indicating whether this device provider detects the logical layout of connected keyboards
+ ///
+ /// Note: is only called when this or
+ /// is .
+ ///
///
public bool CanDetectLogicalLayout { get; protected set; }
@@ -55,33 +62,27 @@ namespace Artemis.Core.DeviceProviders
///
/// The device to load the layout for
/// The resulting Artemis layout
- public virtual ArtemisLayout LoadLayout(IRGBDevice rgbDevice)
+ public virtual ArtemisLayout LoadLayout(ArtemisDevice rgbDevice)
{
- // Take out invalid file name chars, may not be perfect but neither are you
- string model = Path.GetInvalidFileNameChars().Aggregate(rgbDevice.DeviceInfo.Model, (current, c) => current.Replace(c, '-'));
string layoutDir = Path.Combine(Plugin.Directory.FullName, "Layouts");
- string filePath;
- // if (rgbDevice.DeviceInfo is IPhysicalLayoutDeviceInfo)
- // {
- // filePath = Path.Combine(
- // layoutDir,
- // rgbDevice.DeviceInfo.Manufacturer,
- // rgbDevice.DeviceInfo.DeviceType.ToString(),
- // model,
- // keyboard.DeviceInfo.
- // ) + ".xml";
- // }
- // else
- // {
- filePath = Path.Combine(
+ string filePath = Path.Combine(
layoutDir,
- rgbDevice.DeviceInfo.Manufacturer,
- rgbDevice.DeviceInfo.DeviceType.ToString(),
- model
- ) + ".xml";
- // }
-
+ rgbDevice.RgbDevice.DeviceInfo.Manufacturer,
+ rgbDevice.RgbDevice.DeviceInfo.DeviceType.ToString(),
+ rgbDevice.GetLayoutFileName()
+ );
return new ArtemisLayout(filePath);
}
+
+ ///
+ /// Called when a specific RGB device's logical and physical layout must be detected
+ ///
+ /// Note: Only called when or is .
+ ///
+ ///
+ /// The device to detect the layout for, always a keyboard
+ public virtual void DetectDeviceLayout(ArtemisDevice rgbDevice)
+ {
+ }
}
}
\ No newline at end of file
diff --git a/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs b/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs
index b952fc74c..20c986cd1 100644
--- a/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs
+++ b/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs
@@ -20,6 +20,9 @@ namespace Artemis.Storage.Entities.Surface
public double BlueScale { get; set; }
public bool IsEnabled { get; set; }
+ public string PhysicalLayout { get; set; }
+ public string LogicalLayout { get; set; }
+
public List InputIdentifiers { get; set; }
}
diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs
index 0b3410643..fa6667b99 100644
--- a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs
+++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs
@@ -235,8 +235,8 @@ namespace Artemis.UI.Shared
UpdateTransform();
// Load the device main image
- // if (Device.RgbDevice.DeviceInfo?.Image?.AbsolutePath != null && File.Exists(Device.RgbDevice.DeviceInfo.Image.AbsolutePath))
- // _deviceImage = new BitmapImage(Device.RgbDevice.DeviceInfo.Image);
+ if (Device.Layout?.Image != null && File.Exists(Device.Layout.Image.LocalPath))
+ _deviceImage = new BitmapImage(Device.Layout.Image);
// Create all the LEDs
foreach (ArtemisLed artemisLed in Device.Leds)
diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs
index 724a8555c..c3bc25d34 100644
--- a/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs
+++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs
@@ -11,6 +11,8 @@ namespace Artemis.UI.Shared
{
internal class DeviceVisualizerLed
{
+ private SolidColorBrush? _renderColorBrush;
+
public DeviceVisualizerLed(ArtemisLed led)
{
Led = led;
@@ -43,9 +45,9 @@ namespace Artemis.UI.Shared
byte g = originalColor.GetG();
byte b = originalColor.GetB();
- drawingContext.DrawRectangle(isDimmed
- ? new SolidColorBrush(Color.FromArgb(100, r, g, b))
- : new SolidColorBrush(Color.FromRgb(r, g, b)), null, LedRect);
+ _renderColorBrush ??= new SolidColorBrush();
+ _renderColorBrush.Color = isDimmed ? Color.FromArgb(100, r, g, b) : Color.FromRgb(r, g, b);
+ drawingContext.DrawRectangle(_renderColorBrush, null, LedRect);
}
public void RenderImage(DrawingContext drawingContext)