using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Artemis.Core.DeviceProviders; using Artemis.Storage.Entities.Surface; using RGB.NET.Core; using RGB.NET.Layout; using SkiaSharp; namespace Artemis.Core { /// /// Represents an RGB device usable by Artemis, provided by a /// public class ArtemisDevice : CorePropertyChanged { private SKPath? _path; private SKRect _rectangle; internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider, ArtemisSurface surface) { DeviceEntity = new DeviceEntity(); RgbDevice = rgbDevice; DeviceProvider = deviceProvider; Surface = surface; Rotation = 0; Scale = 1; ZIndex = 1; RedScale = 1; GreenScale = 1; BlueScale = 1; IsEnabled = true; InputIdentifiers = new List(); Leds = rgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly(); LedIds = new ReadOnlyDictionary(Leds.ToDictionary(l => l.RgbLed.Id, l => l)); LoadBestLayout(); ApplyToEntity(); CalculateRenderProperties(); } internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider, ArtemisSurface surface, DeviceEntity deviceEntity) { DeviceEntity = deviceEntity; RgbDevice = rgbDevice; DeviceProvider = deviceProvider; Surface = surface; InputIdentifiers = new List(); foreach (DeviceInputIdentifierEntity identifierEntity in DeviceEntity.InputIdentifiers) InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(identifierEntity.InputProvider, identifierEntity.Identifier)); Leds = rgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly(); LedIds = new ReadOnlyDictionary(Leds.ToDictionary(l => l.RgbLed.Id, l => l)); LoadBestLayout(); } /// /// Gets the rectangle covering the device /// public SKRect Rectangle { get => _rectangle; private set => SetAndNotify(ref _rectangle, value); } /// /// Gets the path surrounding the device /// public SKPath? Path { get => _path; private set => SetAndNotify(ref _path, value); } /// /// Gets the RGB.NET device backing this Artemis device /// public IRGBDevice RgbDevice { get; } /// /// Gets the device provider that provided this device /// public DeviceProvider DeviceProvider { get; } /// /// Gets the surface containing this device /// public ArtemisSurface Surface { get; } /// /// Gets a read only collection containing the LEDs of this device /// 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; private set; } /// /// Gets a list of input identifiers associated with this device /// public List InputIdentifiers { get; } /// /// Gets or sets the X-position of the device /// public double X { get => DeviceEntity.X; set { DeviceEntity.X = value; OnPropertyChanged(nameof(X)); } } /// /// Gets or sets the Y-position of the device /// public double Y { get => DeviceEntity.Y; set { DeviceEntity.Y = value; OnPropertyChanged(nameof(Y)); } } /// /// Gets or sets the rotation of the device /// public double Rotation { get => DeviceEntity.Rotation; set { DeviceEntity.Rotation = value; OnPropertyChanged(nameof(Rotation)); } } /// /// Gets or sets the scale of the device /// public double Scale { get => DeviceEntity.Scale; set { DeviceEntity.Scale = value; OnPropertyChanged(nameof(Scale)); } } /// /// Gets or sets the Z-index of the device /// public int ZIndex { get => DeviceEntity.ZIndex; set { DeviceEntity.ZIndex = value; OnPropertyChanged(nameof(ZIndex)); } } /// /// Gets or sets the scale of the red color component used for calibration /// public double RedScale { get => DeviceEntity.RedScale; set { DeviceEntity.RedScale = value; OnPropertyChanged(nameof(RedScale)); } } /// /// Gets or sets the scale of the green color component used for calibration /// public double GreenScale { get => DeviceEntity.GreenScale; set { DeviceEntity.GreenScale = value; OnPropertyChanged(nameof(GreenScale)); } } /// /// Gets or sets the scale of the blue color component used for calibration /// public double BlueScale { get => DeviceEntity.BlueScale; set { DeviceEntity.BlueScale = value; OnPropertyChanged(nameof(BlueScale)); } } /// /// Gets or sets a boolean indicating whether this devices is enabled or not /// public bool IsEnabled { get => DeviceEntity.IsEnabled; set { DeviceEntity.IsEnabled = value; OnPropertyChanged(nameof(IsEnabled)); } } /// /// 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 /// public ArtemisLayout? Layout { get; internal set; } internal DeviceEntity DeviceEntity { get; } /// public override string ToString() { return $"[{RgbDevice.DeviceInfo.DeviceType}] {RgbDevice.DeviceInfo.DeviceName} - {X}.{Y}.{ZIndex}"; } /// /// Attempts to retrieve the that corresponds the provided RGB.NET /// /// The RGB.NET to find the corresponding for /// If found, the corresponding ; otherwise . public ArtemisLed? GetLed(Led led) { return GetLed(led.Id); } /// /// Attempts to retrieve the that corresponds the provided RGB.NET /// /// The RGB.NET to find the corresponding for /// If found, the corresponding ; otherwise . public ArtemisLed? GetLed(LedId ledId) { LedIds.TryGetValue(ledId, out ArtemisLed? artemisLed); 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() { // 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); // 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); Leds = RgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly(); LedIds = new ReadOnlyDictionary(Leds.ToDictionary(l => l.RgbLed.Id, l => l)); Layout = layout; Layout.ApplyDevice(this); } internal void ApplyToEntity() { // Other properties are computed DeviceEntity.DeviceIdentifier = RgbDevice.GetDeviceIdentifier(); DeviceEntity.InputIdentifiers.Clear(); foreach (ArtemisDeviceInputIdentifier identifier in InputIdentifiers) DeviceEntity.InputIdentifiers.Add(new DeviceInputIdentifierEntity { InputProvider = identifier.InputProvider, Identifier = identifier.Identifier }); } internal void ApplyToRgbDevice() { RgbDevice.Rotation = DeviceEntity.Rotation; RgbDevice.Scale = DeviceEntity.Scale; // Workaround for device rotation not applying if (DeviceEntity.X == 0 && DeviceEntity.Y == 0) RgbDevice.Location = new Point(1, 1); RgbDevice.Location = new Point(DeviceEntity.X, DeviceEntity.Y); InputIdentifiers.Clear(); foreach (DeviceInputIdentifierEntity identifierEntity in DeviceEntity.InputIdentifiers) InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(identifierEntity.InputProvider, identifierEntity.Identifier)); CalculateRenderProperties(); OnDeviceUpdated(); } internal void CalculateRenderProperties() { Rectangle = RgbDevice.Boundary.ToSKRect(); if (!Leds.Any()) return; foreach (ArtemisLed led in Leds) led.CalculateRectangles(); SKPath path = new() {FillType = SKPathFillType.Winding}; foreach (ArtemisLed artemisLed in Leds) path.AddRect(artemisLed.AbsoluteRectangle); Path = path; } #region Events /// /// Occurs when the underlying RGB.NET device was updated /// public event EventHandler? DeviceUpdated; /// /// Invokes the event /// protected virtual void OnDeviceUpdated() { DeviceUpdated?.Invoke(this, EventArgs.Empty); } #endregion } }