diff --git a/src/Artemis.Core/Events/DeviceConfigurationEventArgs.cs b/src/Artemis.Core/Events/DeviceConfigurationEventArgs.cs deleted file mode 100644 index dbc66515e..000000000 --- a/src/Artemis.Core/Events/DeviceConfigurationEventArgs.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace Artemis.Core -{ - /// - /// Provides data about surface configuration related events - /// - public class SurfaceConfigurationEventArgs : EventArgs - { - internal SurfaceConfigurationEventArgs(ArtemisSurface surface) - { - Surface = surface; - } - - /// - /// Gets the active surface at the time the event fired - /// - public ArtemisSurface Surface { get; } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Events/DeviceEventArgs.cs b/src/Artemis.Core/Events/DeviceEventArgs.cs index 3999e9f83..3eaf045bf 100644 --- a/src/Artemis.Core/Events/DeviceEventArgs.cs +++ b/src/Artemis.Core/Events/DeviceEventArgs.cs @@ -1,5 +1,4 @@ using System; -using RGB.NET.Core; namespace Artemis.Core { @@ -8,7 +7,7 @@ namespace Artemis.Core /// public class DeviceEventArgs : EventArgs { - internal DeviceEventArgs(IRGBDevice device) + internal DeviceEventArgs(ArtemisDevice device) { Device = device; } @@ -16,6 +15,6 @@ namespace Artemis.Core /// /// Gets the device this event is related to /// - public IRGBDevice Device { get; } + public ArtemisDevice Device { get; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Events/SurfaceConfigurationEventArgs.cs b/src/Artemis.Core/Events/SurfaceConfigurationEventArgs.cs new file mode 100644 index 000000000..52f8192a9 --- /dev/null +++ b/src/Artemis.Core/Events/SurfaceConfigurationEventArgs.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace Artemis.Core +{ + /// + /// Provides data about device configuration related events + /// + public class SurfaceConfigurationEventArgs : EventArgs + { + internal SurfaceConfigurationEventArgs(List devices) + { + Devices = devices; + } + + /// + /// Gets the current list of devices + /// + public List Devices { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index bd2a17907..ace2734a9 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -567,7 +567,7 @@ namespace Artemis.Core CalculateRenderProperties(); } - internal void PopulateLeds(ArtemisSurface surface) + internal void PopulateLeds(IEnumerable devices) { if (Disposed) throw new ObjectDisposedException("Layer"); @@ -575,7 +575,7 @@ namespace Artemis.Core List leds = new(); // Get the surface LEDs for this layer - List availableLeds = surface.Devices.Where(d => d.IsEnabled).SelectMany(d => d.Leds).ToList(); + List availableLeds = devices.SelectMany(d => d.Leds).ToList(); foreach (LedEntity ledEntity in LayerEntity.Leds) { ArtemisLed? match = availableLeds.FirstOrDefault(a => a.Device.RgbDevice.GetDeviceIdentifier() == ledEntity.DeviceIdentifier && diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index 28ee0ac5a..1432d3157 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -124,14 +124,14 @@ namespace Artemis.Core /// /// Populates all the LEDs on the elements in this profile /// - /// The currently active surface that contains the LEDs - public void PopulateLeds(ArtemisSurface surface) + /// The devices to use while populating LEDs + public void PopulateLeds(IEnumerable devices) { if (Disposed) throw new ObjectDisposedException("Profile"); foreach (Layer layer in GetAllLayers()) - layer.PopulateLeds(surface); + layer.PopulateLeds(devices); } /// @@ -197,7 +197,7 @@ namespace Artemis.Core ProfileEntity.Layers.AddRange(GetAllLayers().Select(f => f.LayerEntity)); } - internal void Activate(ArtemisSurface surface) + internal void Activate(IEnumerable devices) { lock (_lock) { @@ -206,7 +206,7 @@ namespace Artemis.Core if (IsActivated) return; - PopulateLeds(surface); + PopulateLeds(devices); OnActivated(); IsActivated = true; } diff --git a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs index e42a17541..9f39a9864 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Artemis.Core.DeviceProviders; +using Artemis.Core.Services; using Artemis.Storage.Entities.Surface; using RGB.NET.Core; using RGB.NET.Layout; @@ -18,12 +19,11 @@ namespace Artemis.Core private SKPath? _path; private SKRect _rectangle; - internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider, ArtemisSurface surface) + internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider) { DeviceEntity = new DeviceEntity(); RgbDevice = rgbDevice; DeviceProvider = deviceProvider; - Surface = surface; Rotation = 0; Scale = 1; @@ -43,12 +43,11 @@ namespace Artemis.Core CalculateRenderProperties(); } - internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider, ArtemisSurface surface, DeviceEntity deviceEntity) + internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider, DeviceEntity deviceEntity) { DeviceEntity = deviceEntity; RgbDevice = rgbDevice; DeviceProvider = deviceProvider; - Surface = surface; InputIdentifiers = new List(); foreach (DeviceInputIdentifierEntity identifierEntity in DeviceEntity.InputIdentifiers) @@ -88,11 +87,6 @@ namespace Artemis.Core /// 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 /// @@ -214,12 +208,13 @@ namespace Artemis.Core } /// - /// Gets or sets a boolean indicating whether this devices is enabled or not + /// Gets a boolean indicating whether this devices is enabled or not + /// Note: To enable/disable a device use the methods provided by /// public bool IsEnabled { get => DeviceEntity.IsEnabled; - set + internal set { DeviceEntity.IsEnabled = value; OnPropertyChanged(nameof(IsEnabled)); @@ -305,37 +300,6 @@ namespace Artemis.Core return fileName; } - /// - /// Loads the best layout for this device, preferring user layouts and falling back to default layouts - /// - public void LoadBestLayout() - { - - - // 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); - } - - private void ApplyKeyboardLayout() - { - if (!(RgbDevice is IKeyboard keyboard)) - return; - - // If supported, detect the device layout so that we can load the correct one - if (DeviceProvider.CanDetectLogicalLayout ) - LogicalLayout = DeviceProvider.GetLogicalLayout(keyboard); - if (DeviceProvider.CanDetectPhysicalLayout) - PhysicalLayout = (KeyboardLayoutType) keyboard.DeviceInfo.Layout; - } - /// /// Applies the provided layout to the device /// @@ -350,12 +314,13 @@ namespace Artemis.Core Layout = layout; Layout.ApplyDevice(this); + OnDeviceUpdated(); } internal void ApplyToEntity() { // Other properties are computed - DeviceEntity.DeviceIdentifier = RgbDevice.GetDeviceIdentifier(); + DeviceEntity.Id = RgbDevice.GetDeviceIdentifier(); DeviceEntity.InputIdentifiers.Clear(); foreach (ArtemisDeviceInputIdentifier identifier in InputIdentifiers) @@ -400,6 +365,18 @@ namespace Artemis.Core Path = path; } + private void ApplyKeyboardLayout() + { + if (!(RgbDevice is IKeyboard keyboard)) + return; + + // If supported, detect the device layout so that we can load the correct one + if (DeviceProvider.CanDetectLogicalLayout) + LogicalLayout = DeviceProvider.GetLogicalLayout(keyboard); + if (DeviceProvider.CanDetectPhysicalLayout) + PhysicalLayout = (KeyboardLayoutType) keyboard.DeviceInfo.Layout; + } + #region Events /// diff --git a/src/Artemis.Core/Models/Surface/ArtemisSurface.cs b/src/Artemis.Core/Models/Surface/ArtemisSurface.cs deleted file mode 100644 index c94f26543..000000000 --- a/src/Artemis.Core/Models/Surface/ArtemisSurface.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using Artemis.Storage.Entities.Surface; -using RGB.NET.Core; - -namespace Artemis.Core -{ - /// - /// Represents a surface of a specific scale, containing all the available s - /// - public class ArtemisSurface : CorePropertyChanged - { - private List _devices = new(); - private ReadOnlyDictionary _ledMap = new(new Dictionary()); - private bool _isActive; - private string _name; - - internal ArtemisSurface(RGBSurface rgbSurface, string name) - { - SurfaceEntity = new SurfaceEntity {DeviceEntities = new List()}; - EntityId = Guid.NewGuid(); - RgbSurface = rgbSurface; - - _name = name; - _isActive = false; - - ApplyToEntity(); - } - - internal ArtemisSurface(RGBSurface rgbSurface, SurfaceEntity surfaceEntity) - { - SurfaceEntity = surfaceEntity; - EntityId = surfaceEntity.Id; - RgbSurface = rgbSurface; - - _name = surfaceEntity.Name; - _isActive = surfaceEntity.IsActive; - } - - /// - /// Gets the RGB.NET surface backing this Artemis surface - /// - public RGBSurface RgbSurface { get; } - - /// - /// Gets the name of the surface - /// - public string Name - { - get => _name; - set => SetAndNotify(ref _name, value); - } - - /// - /// Gets a boolean indicating whether this surface is the currently active surface - /// - public bool IsActive - { - get => _isActive; - internal set => SetAndNotify(ref _isActive, value); - } - - /// - /// Gets a list of devices this surface contains - /// - public List Devices - { - get => _devices; - internal set => SetAndNotify(ref _devices, value); - } - - /// - /// Gets a dictionary containing all s on the surface with their corresponding RGB.NET - /// as key - /// - public ReadOnlyDictionary LedMap - { - get => _ledMap; - private set => SetAndNotify(ref _ledMap, value); - } - - internal SurfaceEntity SurfaceEntity { get; set; } - internal Guid EntityId { get; set; } - - /// - /// 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? GetArtemisLed(Led led) - { - LedMap.TryGetValue(led, out ArtemisLed? artemisLed); - return artemisLed; - } - - internal void UpdateLedMap() - { - LedMap = new ReadOnlyDictionary( - _devices.Where(d => d.IsEnabled).SelectMany(d => d.Leds.Select(al => new KeyValuePair(al.RgbLed, al))).ToDictionary(kvp => kvp.Key, kvp => kvp.Value) - ); - } - - internal void ApplyToEntity() - { - SurfaceEntity.Id = EntityId; - SurfaceEntity.Name = Name; - SurfaceEntity.IsActive = IsActive; - - // Add missing device entities, don't remove old ones in case they come back later - foreach (DeviceEntity deviceEntity in Devices.Select(d => d.DeviceEntity).ToList()) - if (!SurfaceEntity.DeviceEntities.Contains(deviceEntity)) - SurfaceEntity.DeviceEntities.Add(deviceEntity); - } - - #region Events - - /// - /// Occurs when the scale of the surface is changed - /// - public event EventHandler? ScaleChanged; - - /// - /// Invokes the event - /// - protected virtual void OnScaleChanged() - { - ScaleChanged?.Invoke(this, EventArgs.Empty); - } - - #endregion - } -} \ 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 index e6ff14470..672594b9a 100644 --- a/src/Artemis.Core/Models/Surface/Layout/ArtemisLayout.cs +++ b/src/Artemis.Core/Models/Surface/Layout/ArtemisLayout.cs @@ -20,6 +20,53 @@ namespace Artemis.Core FilePath = filePath; Leds = new List(); + LoadLayout(); + } + + /// + /// 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; private set; } + + /// + /// 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; private set; } + + /// + /// Gets the image of the device + /// + public Uri? Image { get; private set; } + + public List Leds { get; } + + internal LayoutCustomDeviceData LayoutCustomDeviceData { get; set; } + + public void ReloadFromDisk() + { + Leds.Clear(); + LoadLayout(); + } + + internal void ApplyDevice(ArtemisDevice artemisDevice) + { + Device = artemisDevice; + foreach (ArtemisLedLayout artemisLedLayout in Leds) + artemisLedLayout.ApplyDevice(Device); + } + + private void LoadLayout() + { DeviceLayout? deviceLayout = DeviceLayout.Load(FilePath, typeof(LayoutCustomDeviceData), typeof(LayoutCustomLedData)); if (deviceLayout != null) { @@ -39,47 +86,13 @@ namespace Artemis.Core 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 the image of the device - /// - public Uri? Image { get; private 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)); + else + Image = null; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Modules/Module.cs b/src/Artemis.Core/Plugins/Modules/Module.cs index 99a7b5980..70d4e5bac 100644 --- a/src/Artemis.Core/Plugins/Modules/Module.cs +++ b/src/Artemis.Core/Plugins/Modules/Module.cs @@ -146,10 +146,9 @@ namespace Artemis.Core.Modules /// Called each frame when the module should render /// /// Time since the last render - /// The RGB Surface to render to /// /// - public abstract void Render(double deltaTime, ArtemisSurface surface, SKCanvas canvas, SKImageInfo canvasInfo); + public abstract void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo); /// /// Called when the are met or during an override @@ -191,9 +190,9 @@ namespace Artemis.Core.Modules Update(deltaTime); } - internal virtual void InternalRender(double deltaTime, ArtemisSurface surface, SKCanvas canvas, SKImageInfo canvasInfo) + internal virtual void InternalRender(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo) { - Render(deltaTime, surface, canvas, canvasInfo); + Render(deltaTime, canvas, canvasInfo); } internal virtual void Activate(bool isOverride) diff --git a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs index 6571d4c89..94fbd3125 100644 --- a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs +++ b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs @@ -142,10 +142,9 @@ namespace Artemis.Core.Modules /// Called after the profile has rendered /// /// Time since the last render - /// The RGB Surface to render to /// /// - public virtual void ProfileRendered(double deltaTime, ArtemisSurface surface, SKCanvas canvas, SKImageInfo canvasInfo) + public virtual void ProfileRendered(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo) { } @@ -168,9 +167,9 @@ namespace Artemis.Core.Modules ProfileUpdated(deltaTime); } - internal override void InternalRender(double deltaTime, ArtemisSurface surface, SKCanvas canvas, SKImageInfo canvasInfo) + internal override void InternalRender(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo) { - Render(deltaTime, surface, canvas, canvasInfo); + Render(deltaTime, canvas, canvasInfo); lock (_lock) { @@ -178,10 +177,10 @@ namespace Artemis.Core.Modules ActiveProfile?.Render(canvas); } - ProfileRendered(deltaTime, surface, canvas, canvasInfo); + ProfileRendered(deltaTime, canvas, canvasInfo); } - internal async Task ChangeActiveProfileAnimated(Profile? profile, ArtemisSurface? surface) + internal async Task ChangeActiveProfileAnimated(Profile? profile, IEnumerable devices) { if (profile != null && profile.Module != this) throw new ArtemisCoreException($"Cannot activate a profile of module {profile.Module} on a module of plugin {this}."); @@ -196,21 +195,19 @@ namespace Artemis.Core.Modules while (OpacityOverride > 0) await Task.Delay(50); - ChangeActiveProfile(profile, surface); + ChangeActiveProfile(profile, devices); AnimatingProfileChange = false; while (OpacityOverride < 1) await Task.Delay(50); } - internal void ChangeActiveProfile(Profile? profile, ArtemisSurface? surface) + internal void ChangeActiveProfile(Profile? profile, IEnumerable devices) { if (profile != null && profile.Module != this) throw new ArtemisCoreException($"Cannot activate a profile of module {profile.Module} on a module of plugin {this}."); if (!IsActivated) throw new ArtemisCoreException("Cannot activate a profile on a deactivated module"); - if (profile != null && surface == null) - throw new ArtemisCoreException("If changing the active profile to a non-null profile, a surface is required"); lock (_lock) { @@ -220,7 +217,7 @@ namespace Artemis.Core.Modules ActiveProfile?.Dispose(); ActiveProfile = profile; - ActiveProfile?.Activate(surface!); + ActiveProfile?.Activate(devices); } OnActiveProfileChanged(); diff --git a/src/Artemis.Core/RGB.NET/BitmapBrush.cs b/src/Artemis.Core/RGB.NET/BitmapBrush.cs index 59814336e..11021f317 100644 --- a/src/Artemis.Core/RGB.NET/BitmapBrush.cs +++ b/src/Artemis.Core/RGB.NET/BitmapBrush.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Artemis.Core.Services; using RGB.NET.Core; using SkiaSharp; @@ -12,13 +13,15 @@ namespace Artemis.Core { private readonly object _disposeLock; private readonly PluginSetting _sampleSizeSetting; + private readonly IRgbService _rgbService; #region Constructors - internal BitmapBrush(Scale scale, PluginSetting sampleSizeSetting) + internal BitmapBrush(Scale scale, PluginSetting sampleSizeSetting, IRgbService rgbService) { _disposeLock = new object(); _sampleSizeSetting = sampleSizeSetting; + _rgbService = rgbService; Scale = scale; } @@ -103,7 +106,7 @@ namespace Artemis.Core if (scaledLocation.X < Bitmap.Width && scaledLocation.Y < Bitmap.Height) { Color pixel = Bitmap.GetPixel(scaledLocation.X.RoundToInt(), scaledLocation.Y.RoundToInt()).ToRgbColor(); - ArtemisDevice? artemisDevice = Surface?.GetArtemisLed(renderTarget.Led)?.Device; + ArtemisDevice? artemisDevice = _rgbService.GetLed(renderTarget.Led)?.Device; if (artemisDevice != null) pixel = pixel.MultiplyRGB(artemisDevice.RedScale, artemisDevice.GreenScale, artemisDevice.BlueScale); RenderedTargets[renderTarget] = pixel; @@ -157,7 +160,7 @@ namespace Artemis.Core Color pixel = new(a / sampleSize, r / sampleSize, g / sampleSize, b / sampleSize); - ArtemisDevice? artemisDevice = Surface?.GetArtemisLed(renderTarget.Led)?.Device; + ArtemisDevice? artemisDevice = _rgbService.GetLed(renderTarget.Led)?.Device; if (artemisDevice is not null) pixel = pixel.MultiplyRGB(artemisDevice.RedScale, artemisDevice.GreenScale, artemisDevice.BlueScale); @@ -188,7 +191,6 @@ namespace Artemis.Core } internal bool IsDisposed { get; set; } - internal ArtemisSurface? Surface { get; set; } #endregion } diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 86c8cb571..ff820f4cb 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -34,7 +34,6 @@ namespace Artemis.Core.Services private readonly IProfileService _profileService; private readonly PluginSetting _renderScale; private readonly IRgbService _rgbService; - private readonly ISurfaceService _surfaceService; private readonly List _updateExceptions = new(); private List _dataModelExpansions = new(); private DateTime _lastExceptionLog; @@ -47,7 +46,6 @@ namespace Artemis.Core.Services ISettingsService settingsService, IPluginManagementService pluginManagementService, IRgbService rgbService, - ISurfaceService surfaceService, IProfileService profileService, IModuleService moduleService // injected to ensure module priorities get applied ) @@ -58,7 +56,6 @@ namespace Artemis.Core.Services _logger = logger; _pluginManagementService = pluginManagementService; _rgbService = rgbService; - _surfaceService = surfaceService; _profileService = profileService; _loggingLevel = settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Debug); _renderScale = settingsService.GetSetting("Core.RenderScale", 0.5); @@ -121,9 +118,6 @@ namespace Artemis.Core.Services IsElevated ); - ArtemisSurface surfaceConfig = _surfaceService.ActiveSurface; - _logger.Information("Initialized with active surface entity {surfaceConfig}-{guid}", surfaceConfig.Name, surfaceConfig.EntityId); - OnInitialized(); } @@ -139,7 +133,7 @@ namespace Artemis.Core.Services public void PlayIntroAnimation() { - IntroAnimation intro = new(_logger, _profileService, _surfaceService); + IntroAnimation intro = new(_logger, _profileService, _rgbService.EnabledDevices); // Draw a white overlay over the device void DrawOverlay(object? sender, FrameRenderingEventArgs args) @@ -236,7 +230,7 @@ namespace Artemis.Core.Services if (!ModuleRenderingDisabled) // While non-activated modules may be updated above if they expand the main data model, they may never render foreach (Module module in modules.Where(m => m.IsActivated)) - module.InternalRender(args.DeltaTime, _surfaceService.ActiveSurface, canvas, _rgbService.BitmapBrush.Bitmap.Info); + module.InternalRender(args.DeltaTime, canvas, _rgbService.BitmapBrush.Bitmap.Info); OnFrameRendering(new FrameRenderingEventArgs(canvas, args.DeltaTime, _rgbService.Surface)); } diff --git a/src/Artemis.Core/Services/Input/InputService.cs b/src/Artemis.Core/Services/Input/InputService.cs index dff8d7d91..6ca8aa735 100644 --- a/src/Artemis.Core/Services/Input/InputService.cs +++ b/src/Artemis.Core/Services/Input/InputService.cs @@ -9,15 +9,13 @@ namespace Artemis.Core.Services internal class InputService : IInputService { private readonly ILogger _logger; - private readonly ISurfaceService _surfaceService; + private readonly IRgbService _rgbService; - public InputService(ILogger logger, ISurfaceService surfaceService) + public InputService(ILogger logger, IRgbService rgbService) { _logger = logger; - _surfaceService = surfaceService; + _rgbService = rgbService; - _surfaceService.ActiveSurfaceConfigurationSelected += SurfaceConfigurationChanged; - _surfaceService.SurfaceConfigurationUpdated += SurfaceConfigurationChanged; BustIdentifierCache(); } @@ -91,7 +89,7 @@ namespace Artemis.Core.Services _logger.Debug("Stop identifying device {device}", _identifyingDevice); _identifyingDevice = null; - _surfaceService.UpdateSurfaceConfiguration(_surfaceService.ActiveSurface, true); + _rgbService.SaveDevices(); BustIdentifierCache(); } @@ -123,7 +121,7 @@ namespace Artemis.Core.Services { if (_cachedFallbackKeyboard != null) return _cachedFallbackKeyboard; - _cachedFallbackKeyboard = _surfaceService.ActiveSurface.Devices.FirstOrDefault(d => d.RgbDevice.DeviceInfo.DeviceType == RGBDeviceType.Keyboard); + _cachedFallbackKeyboard = _rgbService.EnabledDevices.FirstOrDefault(d => d.RgbDevice.DeviceInfo.DeviceType == RGBDeviceType.Keyboard); return _cachedFallbackKeyboard; } @@ -131,7 +129,7 @@ namespace Artemis.Core.Services { if (_cachedFallbackMouse != null) return _cachedFallbackMouse; - _cachedFallbackMouse = _surfaceService.ActiveSurface.Devices.FirstOrDefault(d => d.RgbDevice.DeviceInfo.DeviceType == RGBDeviceType.Mouse); + _cachedFallbackMouse = _rgbService.EnabledDevices.FirstOrDefault(d => d.RgbDevice.DeviceInfo.DeviceType == RGBDeviceType.Mouse); return _cachedFallbackMouse; } @@ -144,7 +142,7 @@ namespace Artemis.Core.Services _cachedFallbackKeyboard = null; _cachedFallbackMouse = null; - _devices = _surfaceService.ActiveSurface.Devices.Where(d => d.InputIdentifiers.Any()).ToList(); + _devices = _rgbService.EnabledDevices.Where(d => d.InputIdentifiers.Any()).ToList(); } private void AddDeviceToCache(ArtemisDevice match, InputProvider provider, object identifier) diff --git a/src/Artemis.Core/Services/Interfaces/IRgbService.cs b/src/Artemis.Core/Services/Interfaces/IRgbService.cs index 3e4dbcd6a..3009ecccc 100644 --- a/src/Artemis.Core/Services/Interfaces/IRgbService.cs +++ b/src/Artemis.Core/Services/Interfaces/IRgbService.cs @@ -9,6 +9,22 @@ namespace Artemis.Core.Services /// public interface IRgbService : IArtemisService, IDisposable { + /// + /// Gets a read-only collection containing all enabled devices + /// + IReadOnlyCollection EnabledDevices { get; } + + /// + /// Gets a read-only collection containing all registered devices + /// + IReadOnlyCollection Devices { get; } + + /// + /// Gets a dictionary containing all s on the surface with their corresponding RGB.NET + /// as key + /// + IReadOnlyDictionary LedMap { get; } + /// /// Gets or sets the RGB surface rendering is performed on /// @@ -19,16 +35,6 @@ namespace Artemis.Core.Services /// BitmapBrush? BitmapBrush { get; } - /// - /// Gets the scale the frames are rendered on, a scale of 1.0 means 1 pixel = 1mm - /// - double RenderScale { get; } - - /// - /// Gets all loaded RGB devices - /// - IReadOnlyCollection LoadedDevices { get; } - /// /// Gets the update trigger that drives the render loop /// @@ -46,14 +52,66 @@ namespace Artemis.Core.Services void AddDeviceProvider(IRGBDeviceProvider deviceProvider); /// - /// Occurs when a single device has loaded + /// Removes the given device provider from the /// - event EventHandler DeviceLoaded; + /// + void RemoveDeviceProvider(IRGBDeviceProvider deviceProvider); /// - /// Occurs when a single device has reloaded + /// Applies auto-arranging logic to the surface /// - event EventHandler DeviceReloaded; + void AutoArrangeDevices(); + + /// + /// Applies the best available layout for the given + /// + /// The device to apply the best available layout to + /// The layout that was applied to the device + ArtemisLayout ApplyBestDeviceLayout(ArtemisDevice device); + + /// + /// Apples the provided to the provided + /// + /// + /// + void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout); + + /// + /// Attempts to retrieve the that corresponds the provided RGB.NET + /// + /// + /// + /// The RGB.NET to find the corresponding + /// for + /// + /// If found, the corresponding ; otherwise . + ArtemisDevice? GetDevice(IRGBDevice rgbDevice); + + /// + /// Attempts to retrieve the that corresponds the provided RGB.NET + /// + /// The RGB.NET to find the corresponding for + /// If found, the corresponding ; otherwise . + ArtemisLed? GetLed(Led led); + + /// + /// Saves the configuration of the provided device to persistent storage + /// + /// + void SaveDevice(ArtemisDevice artemisDevice); + + /// + /// Saves the configuration of all current devices to persistent storage + /// + void SaveDevices(); + + void EnableDevice(ArtemisDevice device); + void DisableDevice(ArtemisDevice device); + + /// + /// Occurs when a single device was added + /// + event EventHandler DeviceAdded; /// /// Occurs when a single device was removed @@ -61,9 +119,8 @@ namespace Artemis.Core.Services event EventHandler DeviceRemoved; /// - /// Recalculates the LED group used by the + /// Occurs when the surface has had modifications to its LED collection /// - /// - void UpdateSurfaceLedGroup(ArtemisSurface artemisSurface); + event EventHandler LedsChanged; } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index 218c55990..a494e34eb 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -1,9 +1,13 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; +using Artemis.Core.DeviceProviders; +using Artemis.Core.Services.Models; +using Artemis.Storage.Entities.Surface; +using Artemis.Storage.Repositories.Interfaces; using RGB.NET.Core; using RGB.NET.Groups; -using RGB.NET.Layout; using Serilog; namespace Artemis.Core.Services @@ -13,16 +17,24 @@ namespace Artemis.Core.Services /// internal class RgbService : IRgbService { - private readonly List _loadedDevices; - private readonly ILogger _logger; - private readonly PluginSetting _renderScaleSetting; - private readonly PluginSetting _sampleSizeSetting; - private readonly PluginSetting _targetFrameRateSetting; - private ListLedGroup? _surfaceLedGroup; + private readonly List _devices; + private readonly List _enabledDevices; + private Dictionary _ledMap; - public RgbService(ILogger logger, ISettingsService settingsService) + private readonly ILogger _logger; + private readonly IPluginManagementService _pluginManagementService; + private readonly IDeviceRepository _deviceRepository; + private readonly PluginSetting _renderScaleSetting; + private readonly PluginSetting _targetFrameRateSetting; + private readonly PluginSetting _sampleSizeSetting; + private ListLedGroup? _surfaceLedGroup; + private bool _modifyingProviders; + + public RgbService(ILogger logger, ISettingsService settingsService, IPluginManagementService pluginManagementService, IDeviceRepository deviceRepository) { _logger = logger; + _pluginManagementService = pluginManagementService; + _deviceRepository = deviceRepository; _renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.5); _targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 25); _sampleSizeSetting = settingsService.GetSetting("Core.SampleSize", 1); @@ -33,57 +45,131 @@ namespace Artemis.Core.Services Surface.Exception += SurfaceOnException; _renderScaleSetting.SettingChanged += RenderScaleSettingOnSettingChanged; _targetFrameRateSetting.SettingChanged += TargetFrameRateSettingOnSettingChanged; - _loadedDevices = new List(); + _enabledDevices = new List(); + _devices = new List(); + _ledMap = new Dictionary(); + UpdateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / _targetFrameRateSetting.Value}; Surface.RegisterUpdateTrigger(UpdateTrigger); } + public IReadOnlyCollection EnabledDevices => _enabledDevices.AsReadOnly(); + public IReadOnlyCollection Devices => _devices.AsReadOnly(); + public IReadOnlyDictionary LedMap => new ReadOnlyDictionary(_ledMap); + /// public RGBSurface Surface { get; set; } public TimerUpdateTrigger UpdateTrigger { get; } public BitmapBrush? BitmapBrush { get; private set; } - public IReadOnlyCollection LoadedDevices => _loadedDevices.AsReadOnly(); - public double RenderScale => _renderScaleSetting.Value; + public bool IsRenderPaused { get; set; } public void AddDeviceProvider(IRGBDeviceProvider deviceProvider) { - try + lock (_devices) { - List toRemove = deviceProvider.Devices.Where(d => Surface.Devices.Contains(d)).ToList(); - Surface.Detach(deviceProvider.Devices); - foreach (IRGBDevice rgbDevice in toRemove) - OnDeviceRemoved(new DeviceEventArgs(rgbDevice)); - - deviceProvider.Initialize(RGBDeviceType.All, true); - Surface.Attach(deviceProvider.Devices); - } - catch (Exception e) - { - _logger.Error(e, "Exception during device loading for device provider {deviceProvider}", deviceProvider.GetType().Name); - throw; - } - - if (deviceProvider.Devices == null || !deviceProvider.Devices.Any()) - { - _logger.Warning("Device provider {deviceProvider} has no devices", deviceProvider.GetType().Name); - return; - } - - foreach (IRGBDevice surfaceDevice in deviceProvider.Devices) - { - _logger.Debug("Device provider {deviceProvider} added {deviceName}", deviceProvider.GetType().Name, surfaceDevice.DeviceInfo?.DeviceName); - if (!_loadedDevices.Contains(surfaceDevice)) + try { - _loadedDevices.Add(surfaceDevice); - OnDeviceLoaded(new DeviceEventArgs(surfaceDevice)); + _modifyingProviders = true; + + List toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList(); + Surface.Detach(deviceProvider.Devices); + foreach (ArtemisDevice device in toRemove) + RemoveDevice(device); + + deviceProvider.Initialize(RGBDeviceType.All, true); + Surface.Attach(deviceProvider.Devices); + + if (!deviceProvider.Devices.Any()) + { + _logger.Warning("Device provider {deviceProvider} has no devices", deviceProvider.GetType().Name); + return; + } + + foreach (IRGBDevice rgbDevice in deviceProvider.Devices) + { + ArtemisDevice artemisDevice = GetArtemisDevice(rgbDevice); + AddDevice(artemisDevice); + _logger.Debug("Device provider {deviceProvider} added {deviceName}", deviceProvider.GetType().Name, rgbDevice.DeviceInfo?.DeviceName); + } + + _devices.Sort((a, b) => a.ZIndex - b.ZIndex); + } + catch (Exception e) + { + _logger.Error(e, "Exception during device loading for device provider {deviceProvider}", deviceProvider.GetType().Name); + throw; + } + finally + { + _modifyingProviders = false; + UpdateBitmapBrush(); } - else - OnDeviceReloaded(new DeviceEventArgs(surfaceDevice)); } } + public void RemoveDeviceProvider(IRGBDeviceProvider deviceProvider) + { + lock (_devices) + { + try + { + _modifyingProviders = true; + + List toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList(); + Surface.Detach(deviceProvider.Devices); + foreach (ArtemisDevice device in toRemove) + RemoveDevice(device); + + _devices.Sort((a, b) => a.ZIndex - b.ZIndex); + } + catch (Exception e) + { + _logger.Error(e, "Exception during device removal for device provider {deviceProvider}", deviceProvider.GetType().Name); + throw; + } + finally + { + _modifyingProviders = false; + UpdateBitmapBrush(); + } + } + } + + private void UpdateBitmapBrush() + { + lock (_devices) + { + if (_modifyingProviders) + return; + + _ledMap = new Dictionary(_devices.SelectMany(d => d.Leds).ToDictionary(l => l.RgbLed)); + + if (_surfaceLedGroup == null || BitmapBrush == null) + { + // Apply the application wide brush and decorator + BitmapBrush = new BitmapBrush(new Scale(_renderScaleSetting.Value), _sampleSizeSetting, this); + _surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) {Brush = BitmapBrush}; + OnLedsChanged(); + return; + } + + lock (_surfaceLedGroup) + { + // Clean up the old background + _surfaceLedGroup.Detach(Surface); + + // Apply the application wide brush and decorator + BitmapBrush.Scale = new Scale(_renderScaleSetting.Value); + _surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) {Brush = BitmapBrush}; + OnLedsChanged(); + } + } + } + + #region IDisposable + public void Dispose() { Surface.UnregisterUpdateTrigger(UpdateTrigger); @@ -92,10 +178,154 @@ namespace Artemis.Core.Services Surface.Dispose(); } + #endregion + + #region EnabledDevices + + public void AutoArrangeDevices() + { + SurfaceArrangement surfaceArrangement = SurfaceArrangement.GetDefaultArrangement(); + surfaceArrangement.Arrange(_devices); + SaveDevices(); + } + + public ArtemisLayout ApplyBestDeviceLayout(ArtemisDevice device) + { + // Look for a user layout + // ... here + + // Try loading a device provider layout, if that comes back valid we use that + ArtemisLayout layout = device.DeviceProvider.LoadLayout(device); + + // Finally fall back to a default layout + // .. do it! + + ApplyDeviceLayout(device, layout); + return layout; + } + + public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout) + { + device.ApplyLayout(layout); + // Applying layouts can affect LEDs, update LED group + UpdateBitmapBrush(); + } + + public ArtemisDevice? GetDevice(IRGBDevice rgbDevice) + { + return _devices.FirstOrDefault(d => d.RgbDevice == rgbDevice); + } + + public ArtemisLed? GetLed(Led led) + { + LedMap.TryGetValue(led, out ArtemisLed? artemisLed); + return artemisLed; + } + + public void EnableDevice(ArtemisDevice device) + { + if (device.IsEnabled) + return; + + _enabledDevices.Add(device); + device.IsEnabled = true; + device.ApplyToEntity(); + _deviceRepository.Save(device.DeviceEntity); + + UpdateBitmapBrush(); + OnDeviceAdded(new DeviceEventArgs(device)); + } + + public void DisableDevice(ArtemisDevice device) + { + if (!device.IsEnabled) + return; + + _enabledDevices.Remove(device); + device.IsEnabled = false; + device.ApplyToEntity(); + _deviceRepository.Save(device.DeviceEntity); + + UpdateBitmapBrush(); + OnDeviceRemoved(new DeviceEventArgs(device)); + } + + private void AddDevice(ArtemisDevice device) + { + if (_devices.Any(d => d.RgbDevice == device.RgbDevice)) + throw new ArtemisCoreException("Attempted to add a duplicate device to the RGB Service"); + + device.ApplyToRgbDevice(); + _devices.Add(device); + if (device.IsEnabled) + _enabledDevices.Add(device); + + // Will call UpdateBitmapBrush() + ApplyBestDeviceLayout(device); + OnDeviceAdded(new DeviceEventArgs(device)); + } + + private void RemoveDevice(ArtemisDevice device) + { + _devices.Remove(device); + if (device.IsEnabled) + _enabledDevices.Remove(device); + + UpdateBitmapBrush(); + OnDeviceRemoved(new DeviceEventArgs(device)); + } + + #endregion + + #region Storage + + private ArtemisDevice GetArtemisDevice(IRGBDevice rgbDevice) + { + string deviceIdentifier = rgbDevice.GetDeviceIdentifier(); + DeviceEntity? deviceEntity = _deviceRepository.Get(deviceIdentifier); + DeviceProvider deviceProvider = _pluginManagementService.GetDeviceProviderByDevice(rgbDevice); + + if (deviceEntity != null) + return new ArtemisDevice(rgbDevice, deviceProvider, deviceEntity); + + // Fall back on creating a new device + _logger.Information( + "No device config found for {deviceInfo}, device hash: {deviceHashCode}. Adding a new entry.", + rgbDevice.DeviceInfo, + deviceIdentifier + ); + return new ArtemisDevice(rgbDevice, deviceProvider); + } + + public void SaveDevice(ArtemisDevice artemisDevice) + { + artemisDevice.ApplyToEntity(); + artemisDevice.ApplyToRgbDevice(); + + _deviceRepository.Save(artemisDevice.DeviceEntity); + OnLedsChanged(); + } + + public void SaveDevices() + { + foreach (ArtemisDevice artemisDevice in _devices) + { + artemisDevice.ApplyToEntity(); + artemisDevice.ApplyToRgbDevice(); + } + + _deviceRepository.Save(_devices.Select(d => d.DeviceEntity)); + OnLedsChanged(); + } + + #endregion + + #region Event handlers + private void RenderScaleSettingOnSettingChanged(object? sender, EventArgs e) { // The surface hasn't changed so we can safely reuse it - UpdateSurfaceLedGroup(BitmapBrush?.Surface); + UpdateBitmapBrush(); } private void TargetFrameRateSettingOnSettingChanged(object? sender, EventArgs e) @@ -109,52 +339,29 @@ namespace Artemis.Core.Services throw args.Exception; } + #endregion + #region Events - public event EventHandler? DeviceLoaded; - public event EventHandler? DeviceReloaded; - public event EventHandler DeviceRemoved; + public event EventHandler? DeviceAdded; + public event EventHandler? DeviceRemoved; + public event EventHandler? LedsChanged; - public void UpdateSurfaceLedGroup(ArtemisSurface? artemisSurface) + private void OnDeviceAdded(DeviceEventArgs e) { - if (artemisSurface == null) - return; - - if (_surfaceLedGroup == null || BitmapBrush == null) - { - // Apply the application wide brush and decorator - BitmapBrush = new BitmapBrush(new Scale(_renderScaleSetting.Value), _sampleSizeSetting); - _surfaceLedGroup = new ListLedGroup(Surface, artemisSurface.LedMap.Select(l => l.Key)) {Brush = BitmapBrush}; - return; - } - - lock (_surfaceLedGroup) - { - // Clean up the old background - _surfaceLedGroup.Detach(Surface); - - // Apply the application wide brush and decorator - BitmapBrush.Scale = new Scale(_renderScaleSetting.Value); - BitmapBrush.Surface = artemisSurface; - _surfaceLedGroup = new ListLedGroup(Surface, artemisSurface.LedMap.Select(l => l.Key)) {Brush = BitmapBrush}; - } - } - - private void OnDeviceLoaded(DeviceEventArgs e) - { - DeviceLoaded?.Invoke(this, e); - } - - private void OnDeviceReloaded(DeviceEventArgs e) - { - DeviceReloaded?.Invoke(this, e); + DeviceAdded?.Invoke(this, e); } protected virtual void OnDeviceRemoved(DeviceEventArgs e) { DeviceRemoved?.Invoke(this, e); } - #endregion + protected virtual void OnLedsChanged() + { + LedsChanged?.Invoke(this, EventArgs.Empty); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/Interfaces/ISurfaceService.cs b/src/Artemis.Core/Services/Storage/Interfaces/ISurfaceService.cs deleted file mode 100644 index 630a65220..000000000 --- a/src/Artemis.Core/Services/Storage/Interfaces/ISurfaceService.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.ObjectModel; - -namespace Artemis.Core.Services -{ - /// - /// Provides access to the device surface and its configuration - /// - public interface ISurfaceService : IArtemisService - { - /// - /// Gets the currently active surface entity, to change config use - /// - ArtemisSurface ActiveSurface { get; } - - /// - /// Gets a read-only list of all surface configurations - /// - ReadOnlyCollection SurfaceConfigurations { get; } - - /// - /// Creates a new surface entity with the supplied name - /// - /// The name for the new surface entity - /// - ArtemisSurface CreateSurfaceConfiguration(string name); - - /// - /// Sets the provided entity as active and applies it to the surface - /// - /// The entity to activate and apply - void SetActiveSurfaceConfiguration(ArtemisSurface surface); - - /// - /// Saves the provided surface entity to permanent storage and if config is active, applies it to the surface - /// - /// The entity to save (and apply if active) - /// Whether to also save devices. If false, devices changes won't be applied either - void UpdateSurfaceConfiguration(ArtemisSurface surface, bool includeDevices); - - /// - /// Deletes the supplied surface entity, surface entity may not be the active surface entity - /// - /// The surface entity to delete, may not be the active surface entity - void DeleteSurfaceConfiguration(ArtemisSurface surface); - - /// - /// Applies auto-arranging logic to a surface - /// - /// - /// The surface to apply auto-arrange to. If the - /// is used. - /// - void AutoArrange(ArtemisSurface? artemisSurface = null); - - /// - /// Occurs when the active device entity has been changed - /// - event EventHandler ActiveSurfaceConfigurationSelected; - - /// - /// Occurs when a surface configuration has been updated - /// - event EventHandler SurfaceConfigurationUpdated; - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangement.cs b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangement.cs index a2bfd0b49..ca0d1ce60 100644 --- a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangement.cs +++ b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangement.cs @@ -76,10 +76,10 @@ namespace Artemis.Core.Services.Models return surfaceArrangementType; } - public void Arrange(ArtemisSurface surface) + public void Arrange(List devices) { ArrangedDevices.Clear(); - foreach (ArtemisDevice surfaceDevice in surface.Devices) + foreach (ArtemisDevice surfaceDevice in devices) { surfaceDevice.X = 0; surfaceDevice.Y = 0; @@ -87,14 +87,14 @@ namespace Artemis.Core.Services.Models } foreach (SurfaceArrangementType surfaceArrangementType in Types) - surfaceArrangementType.Arrange(surface); + surfaceArrangementType.Arrange(devices); // See if we need to move the surface to keep X and Y values positive - double x = surface.Devices.Min(d => d.RgbDevice.Location.X); - double y = surface.Devices.Min(d => d.RgbDevice.Location.Y); + double x = devices.Min(d => d.RgbDevice.Location.X); + double y = devices.Min(d => d.RgbDevice.Location.Y); if (x < 0) { - foreach (ArtemisDevice surfaceDevice in surface.Devices) + foreach (ArtemisDevice surfaceDevice in devices) { surfaceDevice.X += x * -1; surfaceDevice.ApplyToRgbDevice(); @@ -103,7 +103,7 @@ namespace Artemis.Core.Services.Models if (y < 0) { - foreach (ArtemisDevice surfaceDevice in surface.Devices) + foreach (ArtemisDevice surfaceDevice in devices) { surfaceDevice.Y += y * -1; surfaceDevice.ApplyToRgbDevice(); diff --git a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementConfiguration.cs b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementConfiguration.cs index fb346fa21..a3ada512d 100644 --- a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementConfiguration.cs +++ b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementConfiguration.cs @@ -32,14 +32,14 @@ namespace Artemis.Core.Services.Models public int MarginBottom { get; } public SurfaceArrangement SurfaceArrangement { get; set; } - public bool Apply(List devices, ArtemisSurface surface) + public bool Apply(List devices) { - if (Anchor != null && !Anchor.HasDevices(surface)) + if (Anchor != null && !Anchor.HasDevices(devices)) return false; // Start at the edge of the anchor, if there is no anchor start at any device - Point startPoint = Anchor?.GetEdge(HorizontalPosition, VerticalPosition, surface) ?? - new SurfaceArrangementType(SurfaceArrangement, RGBDeviceType.All, 1).GetEdge(HorizontalPosition, VerticalPosition, surface); + Point startPoint = Anchor?.GetEdge(HorizontalPosition, VerticalPosition) ?? + new SurfaceArrangementType(SurfaceArrangement, RGBDeviceType.All, 1).GetEdge(HorizontalPosition, VerticalPosition); // Stack multiple devices of the same type vertically if they are wider than they are tall bool stackVertically = devices.Average(d => d.RgbDevice.Size.Width) >= devices.Average(d => d.RgbDevice.Size.Height); diff --git a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementType.cs b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementType.cs index 81b8dec0b..6a2c4f185 100644 --- a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementType.cs +++ b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementType.cs @@ -21,21 +21,21 @@ namespace Artemis.Core.Services.Models public List Configurations { get; } public SurfaceArrangementConfiguration? AppliedConfiguration { get; set; } - public bool HasDevices(ArtemisSurface surface) + public bool HasDevices(List devices) { - return surface.Devices.Any(d => d.RgbDevice.DeviceInfo.DeviceType == DeviceType); + return devices.Any(d => d.RgbDevice.DeviceInfo.DeviceType == DeviceType); } - public void Arrange(ArtemisSurface surface) + public void Arrange(List devices) { - List devices = surface.Devices.Where(d => d.RgbDevice.DeviceInfo.DeviceType == DeviceType).ToList(); + devices = devices.Where(d => d.RgbDevice.DeviceInfo.DeviceType == DeviceType).ToList(); if (!devices.Any()) return; AppliedConfiguration = null; foreach (SurfaceArrangementConfiguration configuration in Configurations) { - bool applied = configuration.Apply(devices, surface); + bool applied = configuration.Apply(devices); if (applied) { AppliedConfiguration = configuration; @@ -52,11 +52,11 @@ namespace Artemis.Core.Services.Models VerticalArrangementPosition.Equal, 10 ) {SurfaceArrangement = SurfaceArrangement}; - fallback.Apply(devices, surface); + fallback.Apply(devices); AppliedConfiguration = fallback; } - public Point GetEdge(HorizontalArrangementPosition horizontalPosition, VerticalArrangementPosition verticalPosition, ArtemisSurface surface) + public Point GetEdge(HorizontalArrangementPosition horizontalPosition, VerticalArrangementPosition verticalPosition) { List devices = SurfaceArrangement.ArrangedDevices.Where(d => d.RgbDevice.DeviceInfo.DeviceType == DeviceType || DeviceType == RGBDeviceType.All).ToList(); if (!devices.Any()) diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 1e5b825e9..e69ca7f4f 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -14,23 +14,22 @@ namespace Artemis.Core.Services { private readonly ILogger _logger; private readonly IPluginManagementService _pluginManagementService; + private readonly IRgbService _rgbService; private readonly IProfileRepository _profileRepository; - private readonly ISurfaceService _surfaceService; public ProfileService(ILogger logger, IPluginManagementService pluginManagementService, - ISurfaceService surfaceService, + IRgbService rgbService, IConditionOperatorService conditionOperatorService, IDataBindingService dataBindingService, IProfileRepository profileRepository) { _logger = logger; _pluginManagementService = pluginManagementService; - _surfaceService = surfaceService; + _rgbService = rgbService; _profileRepository = profileRepository; - _surfaceService.ActiveSurfaceConfigurationSelected += OnActiveSurfaceConfigurationSelected; - _surfaceService.SurfaceConfigurationUpdated += OnSurfaceConfigurationUpdated; + _rgbService.LedsChanged += RgbServiceOnLedsChanged; } public JsonSerializerSettings MementoSettings { get; set; } = new() {TypeNameHandling = TypeNameHandling.All}; @@ -62,12 +61,11 @@ namespace Artemis.Core.Services /// /// Populates all missing LEDs on all currently active profiles /// - /// - private void ActiveProfilesPopulateLeds(ArtemisSurface surface) + private void ActiveProfilesPopulateLeds() { List profileModules = _pluginManagementService.GetFeaturesOfType(); foreach (ProfileModule profileModule in profileModules.Where(p => p.ActiveProfile != null).ToList()) - profileModule.ActiveProfile?.PopulateLeds(surface); // Avoid race condition + profileModule.ActiveProfile?.PopulateLeds(_rgbService.EnabledDevices); // Avoid race condition } public List GetProfileDescriptors(ProfileModule module) @@ -110,7 +108,7 @@ namespace Artemis.Core.Services Profile profile = new(profileDescriptor.ProfileModule, profileEntity); InstantiateProfile(profile); - profileDescriptor.ProfileModule.ChangeActiveProfile(profile, _surfaceService.ActiveSurface); + profileDescriptor.ProfileModule.ChangeActiveProfile(profile, _rgbService.EnabledDevices); SaveActiveProfile(profileDescriptor.ProfileModule); return profile; @@ -124,9 +122,9 @@ namespace Artemis.Core.Services ProfileEntity entity = _profileRepository.Get(module.ActiveProfile.EntityId); Profile profile = new(module, entity); InstantiateProfile(profile); - - module.ChangeActiveProfile(null, _surfaceService.ActiveSurface); - module.ChangeActiveProfile(profile, _surfaceService.ActiveSurface); + + module.ChangeActiveProfile(null, _rgbService.EnabledDevices); + module.ChangeActiveProfile(profile, _rgbService.EnabledDevices); } public async Task ActivateProfileAnimated(ProfileDescriptor profileDescriptor) @@ -141,9 +139,9 @@ namespace Artemis.Core.Services Profile profile = new(profileDescriptor.ProfileModule, profileEntity); InstantiateProfile(profile); - void ActivatingProfileSurfaceUpdate(object? sender, SurfaceConfigurationEventArgs e) + void ActivatingRgbServiceOnLedsChanged(object? sender, EventArgs e) { - profile.PopulateLeds(e.Surface); + profile.PopulateLeds(_rgbService.EnabledDevices); } void ActivatingProfilePluginToggle(object? sender, PluginEventArgs e) @@ -154,28 +152,29 @@ namespace Artemis.Core.Services // This could happen during activation so subscribe to it _pluginManagementService.PluginEnabled += ActivatingProfilePluginToggle; _pluginManagementService.PluginDisabled += ActivatingProfilePluginToggle; - _surfaceService.SurfaceConfigurationUpdated += ActivatingProfileSurfaceUpdate; + _rgbService.LedsChanged += ActivatingRgbServiceOnLedsChanged; - await profileDescriptor.ProfileModule.ChangeActiveProfileAnimated(profile, _surfaceService.ActiveSurface); + await profileDescriptor.ProfileModule.ChangeActiveProfileAnimated(profile, _rgbService.EnabledDevices); SaveActiveProfile(profileDescriptor.ProfileModule); _pluginManagementService.PluginEnabled -= ActivatingProfilePluginToggle; _pluginManagementService.PluginDisabled -= ActivatingProfilePluginToggle; - _surfaceService.SurfaceConfigurationUpdated -= ActivatingProfileSurfaceUpdate; + _rgbService.LedsChanged -= ActivatingRgbServiceOnLedsChanged; return profile; } + public void ClearActiveProfile(ProfileModule module) { - module.ChangeActiveProfile(null, _surfaceService.ActiveSurface); + module.ChangeActiveProfile(null, _rgbService.EnabledDevices); SaveActiveProfile(module); } public async Task ClearActiveProfileAnimated(ProfileModule module) { - await module.ChangeActiveProfileAnimated(null, _surfaceService.ActiveSurface); + await module.ChangeActiveProfileAnimated(null, _rgbService.EnabledDevices); } public void DeleteProfile(Profile profile) @@ -255,7 +254,7 @@ namespace Artemis.Core.Services string top = profile.RedoStack.Pop(); string memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings); profile.UndoStack.Push(memento); - profile.ProfileEntity = JsonConvert.DeserializeObject(top, MementoSettings) + profile.ProfileEntity = JsonConvert.DeserializeObject(top, MementoSettings) ?? throw new InvalidOperationException("Failed to deserialize memento"); profile.Load(); @@ -268,7 +267,7 @@ namespace Artemis.Core.Services public void InstantiateProfile(Profile profile) { - profile.PopulateLeds(_surfaceService.ActiveSurface); + profile.PopulateLeds(_rgbService.EnabledDevices); } public string ExportProfile(ProfileDescriptor profileDescriptor) @@ -296,17 +295,11 @@ namespace Artemis.Core.Services #region Event handlers - private void OnActiveSurfaceConfigurationSelected(object? sender, SurfaceConfigurationEventArgs e) + private void RgbServiceOnLedsChanged(object? sender, EventArgs e) { - ActiveProfilesPopulateLeds(e.Surface); + ActiveProfilesPopulateLeds(); } - - private void OnSurfaceConfigurationUpdated(object? sender, SurfaceConfigurationEventArgs e) - { - if (e.Surface.IsActive) - ActiveProfilesPopulateLeds(e.Surface); - } - + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/SurfaceService.cs b/src/Artemis.Core/Services/Storage/SurfaceService.cs deleted file mode 100644 index 6adbc7bfe..000000000 --- a/src/Artemis.Core/Services/Storage/SurfaceService.cs +++ /dev/null @@ -1,265 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using Artemis.Core.DeviceProviders; -using Artemis.Core.Services.Models; -using Artemis.Storage.Entities.Surface; -using Artemis.Storage.Repositories.Interfaces; -using RGB.NET.Core; -using Serilog; - -namespace Artemis.Core.Services -{ - internal class SurfaceService : ISurfaceService - { - private readonly ILogger _logger; - private readonly IPluginManagementService _pluginManagementService; - private readonly IRgbService _rgbService; - private readonly List _surfaceConfigurations; - private readonly ISurfaceRepository _surfaceRepository; - - public SurfaceService(ILogger logger, ISurfaceRepository surfaceRepository, IRgbService rgbService, IPluginManagementService pluginManagementService) - { - _logger = logger; - _surfaceRepository = surfaceRepository; - _rgbService = rgbService; - _pluginManagementService = pluginManagementService; - _surfaceConfigurations = new List(); - - // LoadFromRepository is guaranteed to set the ActiveSurface - ActiveSurface = null!; - LoadFromRepository(); - - _rgbService.DeviceLoaded += RgbServiceOnDeviceLoaded; - _rgbService.DeviceRemoved += RgbServiceOnDeviceRemoved; - } - - #region Repository - - private void LoadFromRepository() - { - List configs = _surfaceRepository.GetAll(); - foreach (SurfaceEntity surfaceEntity in configs) - { - // Create the surface entity - ArtemisSurface surfaceConfiguration = new(_rgbService.Surface, surfaceEntity); - foreach (DeviceEntity position in surfaceEntity.DeviceEntities) - { - IRGBDevice? device = _rgbService.Surface.Devices.FirstOrDefault(d => d.GetDeviceIdentifier() == position.DeviceIdentifier); - if (device != null) - { - DeviceProvider deviceProvider = _pluginManagementService.GetDeviceProviderByDevice(device); - surfaceConfiguration.Devices.Add(new ArtemisDevice(device, deviceProvider, surfaceConfiguration, position)); - } - } - - // Finally, add the surface config to the collection - lock (_surfaceConfigurations) - { - _surfaceConfigurations.Add(surfaceConfiguration); - } - } - - // When all surface configs are loaded, apply the active surface config - ArtemisSurface? active = SurfaceConfigurations.FirstOrDefault(c => c.IsActive); - if (active != null) - { - SetActiveSurfaceConfiguration(active); - } - else - { - active = SurfaceConfigurations.FirstOrDefault(); - if (active != null) - SetActiveSurfaceConfiguration(active); - else - SetActiveSurfaceConfiguration(CreateSurfaceConfiguration("Default")); - } - } - - #endregion - - #region Utilities - - private void AddDeviceIfMissing(IRGBDevice rgbDevice, ArtemisSurface surface) - { - string deviceIdentifier = rgbDevice.GetDeviceIdentifier(); - ArtemisDevice? device = surface.Devices.FirstOrDefault(d => d.DeviceEntity.DeviceIdentifier == deviceIdentifier); - - if (device != null) - return; - - // Find an existing device config and use that - DeviceEntity? existingDeviceConfig = surface.SurfaceEntity.DeviceEntities.FirstOrDefault(d => d.DeviceIdentifier == deviceIdentifier); - if (existingDeviceConfig != null) - { - DeviceProvider deviceProvider = _pluginManagementService.GetDeviceProviderByDevice(rgbDevice); - device = new ArtemisDevice(rgbDevice, deviceProvider, surface, existingDeviceConfig); - } - // Fall back on creating a new device - else - { - _logger.Information( - "No device config found for {deviceInfo}, device hash: {deviceHashCode}. Adding a new entry.", - rgbDevice.DeviceInfo, - deviceIdentifier - ); - DeviceProvider deviceProvider = _pluginManagementService.GetDeviceProviderByDevice(rgbDevice); - device = new ArtemisDevice(rgbDevice, deviceProvider, surface); - } - - surface.Devices.Add(device); - } - - #endregion - - - public ArtemisSurface ActiveSurface { get; private set; } - public ReadOnlyCollection SurfaceConfigurations => _surfaceConfigurations.AsReadOnly(); - - public ArtemisSurface CreateSurfaceConfiguration(string name) - { - // Create a blank config - ArtemisSurface configuration = new(_rgbService.Surface, name); - - // Add all current devices - foreach (IRGBDevice rgbDevice in _rgbService.LoadedDevices) - { - DeviceProvider deviceProvider = _pluginManagementService.GetDeviceProviderByDevice(rgbDevice); - configuration.Devices.Add(new ArtemisDevice(rgbDevice, deviceProvider, configuration)); - } - - // Auto-arrange the new config - AutoArrange(configuration); - - lock (_surfaceConfigurations) - { - _surfaceRepository.Add(configuration.SurfaceEntity); - _surfaceConfigurations.Add(configuration); - - UpdateSurfaceConfiguration(configuration, true); - return configuration; - } - } - - public void SetActiveSurfaceConfiguration(ArtemisSurface surface) - { - if (surface == null) throw new ArgumentNullException(nameof(surface)); - if (ActiveSurface == surface) - return; - - // Set the new entity - ActiveSurface = surface; - - // Ensure only the new entity is marked as active - lock (_surfaceConfigurations) - { - // Mark only the new surface as active - foreach (ArtemisSurface configuration in _surfaceConfigurations) - { - configuration.IsActive = configuration == ActiveSurface; - configuration.ApplyToEntity(); - - _surfaceRepository.Save(configuration.SurfaceEntity); - } - } - - // Apply the active surface entity to the devices - foreach (ArtemisDevice device in ActiveSurface.Devices) - device.ApplyToRgbDevice(); - - // Update the RGB service's graphics decorator to work with the new surface entity - _rgbService.UpdateSurfaceLedGroup(ActiveSurface); - OnActiveSurfaceConfigurationChanged(new SurfaceConfigurationEventArgs(ActiveSurface)); - } - - public void UpdateSurfaceConfiguration(ArtemisSurface surface, bool includeDevices) - { - surface.ApplyToEntity(); - if (includeDevices) - foreach (ArtemisDevice deviceConfiguration in surface.Devices) - { - deviceConfiguration.ApplyToEntity(); - if (surface.IsActive) - deviceConfiguration.ApplyToRgbDevice(); - } - - surface.UpdateLedMap(); - - _surfaceRepository.Save(surface.SurfaceEntity); - _rgbService.UpdateSurfaceLedGroup(ActiveSurface); - OnSurfaceConfigurationUpdated(new SurfaceConfigurationEventArgs(surface)); - } - - public void DeleteSurfaceConfiguration(ArtemisSurface surface) - { - if (surface == ActiveSurface) - throw new ArtemisCoreException($"Cannot delete surface entity '{surface.Name}' because it is active."); - - lock (_surfaceConfigurations) - { - SurfaceEntity entity = surface.SurfaceEntity; - _surfaceConfigurations.Remove(surface); - _surfaceRepository.Remove(entity); - } - } - - #region AutoLayout - - public void AutoArrange(ArtemisSurface? artemisSurface = null) - { - artemisSurface ??= ActiveSurface; - if (!artemisSurface.Devices.Any()) - return; - - SurfaceArrangement surfaceArrangement = SurfaceArrangement.GetDefaultArrangement(); - surfaceArrangement.Arrange(artemisSurface); - UpdateSurfaceConfiguration(artemisSurface, true); - } - - #endregion - - #region Event handlers - - private void RgbServiceOnDeviceLoaded(object? sender, DeviceEventArgs e) - { - lock (_surfaceConfigurations) - { - foreach (ArtemisSurface surfaceConfiguration in _surfaceConfigurations) - AddDeviceIfMissing(e.Device, surfaceConfiguration); - } - - UpdateSurfaceConfiguration(ActiveSurface, true); - } - - private void RgbServiceOnDeviceRemoved(object? sender, DeviceEventArgs e) - { - lock (_surfaceConfigurations) - { - foreach (ArtemisSurface surfaceConfiguration in _surfaceConfigurations) - surfaceConfiguration.Devices.RemoveAll(d => d.RgbDevice == e.Device); - } - - UpdateSurfaceConfiguration(ActiveSurface, true); - } - - #endregion - - #region Events - - public event EventHandler? ActiveSurfaceConfigurationSelected; - public event EventHandler? SurfaceConfigurationUpdated; - - protected virtual void OnActiveSurfaceConfigurationChanged(SurfaceConfigurationEventArgs e) - { - ActiveSurfaceConfigurationSelected?.Invoke(this, e); - } - - protected virtual void OnSurfaceConfigurationUpdated(SurfaceConfigurationEventArgs e) - { - SurfaceConfigurationUpdated?.Invoke(this, e); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Utilities/IntroAnimation.cs b/src/Artemis.Core/Utilities/IntroAnimation.cs index 56b92f1f5..f2f1bb4fb 100644 --- a/src/Artemis.Core/Utilities/IntroAnimation.cs +++ b/src/Artemis.Core/Utilities/IntroAnimation.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using Artemis.Core.Modules; @@ -13,13 +14,13 @@ namespace Artemis.Core { private readonly ILogger _logger; private readonly IProfileService _profileService; - private readonly ISurfaceService _surfaceService; + private readonly IEnumerable _devices; - public IntroAnimation(ILogger logger, IProfileService profileService, ISurfaceService surfaceService) + public IntroAnimation(ILogger logger, IProfileService profileService, IEnumerable devices) { _logger = logger; _profileService = profileService; - _surfaceService = surfaceService; + _devices = devices; AnimationProfile = CreateIntroProfile(); } @@ -41,14 +42,14 @@ namespace Artemis.Core ProfileEntity profileEntity = CoreJson.DeserializeObject(json)!; // Inject every LED on the surface into each layer foreach (LayerEntity profileEntityLayer in profileEntity.Layers) - profileEntityLayer.Leds.AddRange(_surfaceService.ActiveSurface.Devices.SelectMany(d => d.Leds).Select(l => new LedEntity + profileEntityLayer.Leds.AddRange(_devices.SelectMany(d => d.Leds).Select(l => new LedEntity { DeviceIdentifier = l.Device.RgbDevice.GetDeviceIdentifier(), LedName = l.RgbLed.Id.ToString() })); Profile profile = new(new DummyModule(), profileEntity); - profile.Activate(_surfaceService.ActiveSurface); + profile.Activate(_devices); _profileService.InstantiateProfile(profile); return profile; @@ -79,7 +80,7 @@ namespace Artemis.Core throw new NotImplementedException(); } - public override void Render(double deltaTime, ArtemisSurface surface, SKCanvas canvas, SKImageInfo canvasInfo) + public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo) { throw new NotImplementedException(); } diff --git a/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs b/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs index 843a82bd1..142786138 100644 --- a/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs +++ b/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs @@ -8,8 +8,8 @@ namespace Artemis.Storage.Entities.Surface { InputIdentifiers = new List(); } - - public string DeviceIdentifier { get; set; } + + public string Id { get; set; } public double X { get; set; } public double Y { get; set; } public double Rotation { get; set; } diff --git a/src/Artemis.Storage/Entities/Surface/SurfaceEntity.cs b/src/Artemis.Storage/Entities/Surface/SurfaceEntity.cs deleted file mode 100644 index 2ff49e492..000000000 --- a/src/Artemis.Storage/Entities/Surface/SurfaceEntity.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Artemis.Storage.Entities.Surface -{ - public class SurfaceEntity - { - public Guid Id { get; set; } - - public string Name { get; set; } - public bool IsActive { get; set; } - - public List DeviceEntities { get; set; } - } -} \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/DeviceRepository.cs b/src/Artemis.Storage/Repositories/DeviceRepository.cs new file mode 100644 index 000000000..9892c328e --- /dev/null +++ b/src/Artemis.Storage/Repositories/DeviceRepository.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using Artemis.Storage.Entities.Surface; +using Artemis.Storage.Repositories.Interfaces; +using LiteDB; + +namespace Artemis.Storage.Repositories +{ + internal class DeviceRepository : IDeviceRepository + { + private readonly LiteRepository _repository; + + public DeviceRepository(LiteRepository repository) + { + _repository = repository; + _repository.Database.GetCollection().EnsureIndex(s => s.Id); + } + + public void Add(DeviceEntity deviceEntity) + { + _repository.Insert(deviceEntity); + } + + public void Remove(DeviceEntity deviceEntity) + { + _repository.Delete(deviceEntity.Id); + } + + public DeviceEntity Get(string id) + { + return _repository.FirstOrDefault(s => s.Id == id); + } + + public List GetAll() + { + return _repository.Query().Include(s => s.InputIdentifiers).ToList(); + } + + public void Save(DeviceEntity deviceEntity) + { + _repository.Upsert(deviceEntity); + } + + public void Save(IEnumerable deviceEntities) + { + _repository.Upsert(deviceEntities); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/IDeviceRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IDeviceRepository.cs new file mode 100644 index 000000000..fc2c8375d --- /dev/null +++ b/src/Artemis.Storage/Repositories/Interfaces/IDeviceRepository.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using Artemis.Storage.Entities.Surface; + +namespace Artemis.Storage.Repositories.Interfaces +{ + public interface IDeviceRepository : IRepository + { + void Add(DeviceEntity deviceEntity); + void Remove(DeviceEntity deviceEntity); + DeviceEntity Get(string id); + List GetAll(); + void Save(DeviceEntity deviceEntity); + void Save(IEnumerable deviceEntities); + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/ISurfaceRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/ISurfaceRepository.cs deleted file mode 100644 index afbd7743b..000000000 --- a/src/Artemis.Storage/Repositories/Interfaces/ISurfaceRepository.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using Artemis.Storage.Entities.Surface; - -namespace Artemis.Storage.Repositories.Interfaces -{ - public interface ISurfaceRepository : IRepository - { - void Add(SurfaceEntity surfaceEntity); - void Remove(SurfaceEntity surfaceEntity); - SurfaceEntity GetByName(string name); - List GetAll(); - - void Save(SurfaceEntity surfaceEntity); - } -} \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/SurfaceRepository.cs b/src/Artemis.Storage/Repositories/SurfaceRepository.cs deleted file mode 100644 index 46b8b9e6b..000000000 --- a/src/Artemis.Storage/Repositories/SurfaceRepository.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Artemis.Storage.Entities.Surface; -using Artemis.Storage.Repositories.Interfaces; -using LiteDB; - -namespace Artemis.Storage.Repositories -{ - internal class SurfaceRepository : ISurfaceRepository - { - private readonly LiteRepository _repository; - - public SurfaceRepository(LiteRepository repository) - { - _repository = repository; - _repository.Database.GetCollection().EnsureIndex(s => s.Name); - } - - public void Add(SurfaceEntity surfaceEntity) - { - _repository.Insert(surfaceEntity); - } - - public void Remove(SurfaceEntity surfaceEntity) - { - _repository.Delete(surfaceEntity.Id); - } - - public SurfaceEntity GetByName(string name) - { - return _repository.FirstOrDefault(s => s.Name == name); - } - - public List GetAll() - { - return _repository.Query().Include(s => s.DeviceEntities.Select(de => de.InputIdentifiers)).ToList(); - } - - public void Save(SurfaceEntity surfaceEntity) - { - _repository.Upsert(surfaceEntity); - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs index fa6667b99..a7ce6b3d1 100644 --- a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs +++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs @@ -182,7 +182,11 @@ namespace Artemis.UI.Shared if (_oldDevice != null) { if (Device != null) + { Device.RgbDevice.PropertyChanged -= DevicePropertyChanged; + Device.DeviceUpdated -= DeviceUpdated; + } + _oldDevice = null; } } @@ -228,10 +232,15 @@ namespace Artemis.UI.Shared return; if (_oldDevice != null) + { Device.RgbDevice.PropertyChanged -= DevicePropertyChanged; + Device.DeviceUpdated -= DeviceUpdated; + } + _oldDevice = Device; Device.RgbDevice.PropertyChanged += DevicePropertyChanged; + Device.DeviceUpdated += DeviceUpdated; UpdateTransform(); // Load the device main image @@ -277,13 +286,16 @@ namespace Artemis.UI.Shared InvalidateMeasure(); } - private void DevicePropertyChanged(object? sender, PropertyChangedEventArgs e) + private void DeviceUpdated(object sender, EventArgs e) { - if (e.PropertyName == nameof(Device.RgbDevice.Scale) || e.PropertyName == nameof(Device.RgbDevice.Rotation)) - UpdateTransform(); + SetupForDevice(); } - + private void DevicePropertyChanged(object? sender, PropertyChangedEventArgs e) + { + SetupForDevice(); + } + private void Render() { DrawingContext drawingContext = _backingStore.Open(); diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs index ee678f4fc..720bf0ad3 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs @@ -8,7 +8,6 @@ using Artemis.Core.Modules; using Artemis.Core.Services; using Artemis.Storage.Entities.Profile; using Artemis.UI.Shared.Services.Models; -using Newtonsoft.Json; using Ninject; using Ninject.Parameters; using Serilog; @@ -20,23 +19,23 @@ namespace Artemis.UI.Shared.Services internal class ProfileEditorService : IProfileEditorService { private readonly ICoreService _coreService; - private readonly ISurfaceService _surfaceService; private readonly IKernel _kernel; private readonly ILogger _logger; private readonly IProfileService _profileService; private readonly List _registeredPropertyEditors; + private readonly IRgbService _rgbService; private readonly object _selectedProfileElementLock = new(); private readonly object _selectedProfileLock = new(); private TimeSpan _currentTime; private int _pixelsPerSecond; - public ProfileEditorService(IKernel kernel, ILogger logger, IProfileService profileService, ICoreService coreService, ISurfaceService surfaceService) + public ProfileEditorService(IKernel kernel, ILogger logger, IProfileService profileService, ICoreService coreService, IRgbService rgbService) { _kernel = kernel; _logger = logger; _profileService = profileService; _coreService = coreService; - _surfaceService = surfaceService; + _rgbService = rgbService; _registeredPropertyEditors = new List(); PixelsPerSecond = 100; @@ -351,7 +350,7 @@ namespace Artemis.UI.Shared.Services public List GetLedsInRectangle(Rect rect) { - return _surfaceService.ActiveSurface.Devices.SelectMany(d => d.Leds).Where(led => led.AbsoluteRectangle.IntersectsWith(rect.ToSKRect())).ToList(); + return _rgbService.EnabledDevices.SelectMany(d => d.Leds).Where(led => led.AbsoluteRectangle.IntersectsWith(rect.ToSKRect())).ToList(); } #region Copy/paste @@ -424,7 +423,7 @@ namespace Artemis.UI.Shared.Services if (pasted != null) { - target.Profile.PopulateLeds(_surfaceService.ActiveSurface); + target.Profile.PopulateLeds(_rgbService.EnabledDevices); UpdateSelectedProfile(); ChangeSelectedProfileElement(pasted); } diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index b311fcae7..55ecf0e3a 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -1,360 +1,361 @@  - - WinExe - net5.0-windows - {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - true - Artemis - Artemis - en-US - Provides advanced unified lighting across many different brands RGB peripherals - Copyright © Robert Beekman - 2021 - 2.0.0.0 - bin\$(Platform)\$(Configuration)\ - true - x64 - - - x64 - + + WinExe + net5.0-windows + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + true + Artemis + Artemis + en-US + Provides advanced unified lighting across many different brands RGB peripherals + Copyright © Robert Beekman - 2021 + 2.0.0.0 + bin\$(Platform)\$(Configuration)\ + true + x64 + windows + + + x64 + - - Resources\Images\Logo\logo-512.ico - - - - 2.0.0.0 - 2.0.0 - - - 2.0-{chash:6} - true - true - true - v[0-9]* - true - git - true - - - - - - - false - - - - - - - - - - - - - - - ..\..\..\RGB.NET\bin\net5.0\RGB.NET.Brushes.dll - - - ..\..\..\RGB.NET\bin\net5.0\RGB.NET.Core.dll - - - ..\..\..\RGB.NET\bin\net5.0\RGB.NET.Groups.dll - - - - - - ResXFileCodeGenerator - Designer - Resources.Designer.cs - - - - - true - - - true - - - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - True - True - Settings.settings - - - - - PreserveNewest - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - - - Designer - - - Designer - - - Designer - - - Designer - - - Designer - - - Designer - - - $(DefaultXamlRuntime) - - + + Resources\Images\Logo\logo-512.ico + + + + 2.0.0.0 + 2.0.0 + + + 2.0-{chash:6} + true + true + true + v[0-9]* + true + git + true + + + + + + + false + + + + + + + + + + + + + + + ..\..\..\RGB.NET\bin\net5.0\RGB.NET.Brushes.dll + + + ..\..\..\RGB.NET\bin\net5.0\RGB.NET.Core.dll + + + ..\..\..\RGB.NET\bin\net5.0\RGB.NET.Groups.dll + + + + + + ResXFileCodeGenerator + Designer + Resources.Designer.cs + + + + + true + + + true + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + PreserveNewest + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + $(DefaultXamlRuntime) + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/FolderViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/FolderViewModel.cs index 310e1324f..ba8de22bd 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/FolderViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/FolderViewModel.cs @@ -9,12 +9,12 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem { // I hate this about DI, oh well public FolderViewModel(ProfileElement folder, + IRgbService rgbService, IProfileEditorService profileEditorService, IDialogService dialogService, IProfileTreeVmFactory profileTreeVmFactory, - ILayerBrushService layerBrushService, - ISurfaceService surfaceService) : - base(folder, profileEditorService, dialogService, profileTreeVmFactory, layerBrushService, surfaceService) + ILayerBrushService layerBrushService) : + base(folder, rgbService, profileEditorService, dialogService, profileTreeVmFactory, layerBrushService) { } diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs index b338ddd13..56b559862 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/LayerViewModel.cs @@ -8,12 +8,12 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem public class LayerViewModel : TreeItemViewModel { public LayerViewModel(ProfileElement layer, + IRgbService rgbService, IProfileEditorService profileEditorService, IDialogService dialogService, IProfileTreeVmFactory profileTreeVmFactory, - ILayerBrushService layerBrushService, - ISurfaceService surfaceService) : - base(layer, profileEditorService, dialogService, profileTreeVmFactory, layerBrushService, surfaceService) + ILayerBrushService layerBrushService) : + base(layer, rgbService, profileEditorService, dialogService, profileTreeVmFactory, layerBrushService) { } diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs index a0086373f..08eeaac02 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs @@ -18,23 +18,23 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem { private readonly IDialogService _dialogService; private readonly ILayerBrushService _layerBrushService; + private readonly IRgbService _rgbService; private readonly IProfileEditorService _profileEditorService; private readonly IProfileTreeVmFactory _profileTreeVmFactory; - private readonly ISurfaceService _surfaceService; private ProfileElement _profileElement; protected TreeItemViewModel(ProfileElement profileElement, + IRgbService rgbService, IProfileEditorService profileEditorService, IDialogService dialogService, IProfileTreeVmFactory profileTreeVmFactory, - ILayerBrushService layerBrushService, - ISurfaceService surfaceService) + ILayerBrushService layerBrushService) { + _rgbService = rgbService; _profileEditorService = profileEditorService; _dialogService = dialogService; _profileTreeVmFactory = profileTreeVmFactory; _layerBrushService = layerBrushService; - _surfaceService = surfaceService; ProfileElement = profileElement; @@ -144,7 +144,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem if (brush != null) layer.ChangeLayerBrush(brush); - layer.AddLeds(_surfaceService.ActiveSurface.Devices.SelectMany(d => d.Leds)); + layer.AddLeds(_rgbService.EnabledDevices.SelectMany(d => d.Leds)); _profileEditorService.UpdateSelectedProfile(); _profileEditorService.ChangeSelectedProfileElement(layer); } diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileView.xaml index 1237727f8..8441cd11d 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileView.xaml @@ -99,7 +99,10 @@ - + diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs index e559a30fc..9fae16063 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs @@ -6,48 +6,48 @@ using System.Windows.Input; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Events; -using Artemis.UI.Extensions; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.ProfileEditor.Visualization.Tools; using Artemis.UI.Screens.Shared; using Artemis.UI.Shared.Services; +using SkiaSharp; using Stylet; namespace Artemis.UI.Screens.ProfileEditor.Visualization { public class ProfileViewModel : Conductor.Collection.AllActive, IProfileEditorPanelViewModel, IHandle, IHandle { - private readonly IProfileEditorService _profileEditorService; private readonly ICoreService _coreService; + private readonly IProfileEditorService _profileEditorService; private readonly IProfileLayerVmFactory _profileLayerVmFactory; + private readonly IRgbService _rgbService; private readonly ISettingsService _settingsService; - private readonly ISurfaceService _surfaceService; private readonly IVisualizationToolVmFactory _visualizationToolVmFactory; private int _activeToolIndex; private VisualizationToolViewModel _activeToolViewModel; + private PluginSetting _alwaysApplyDataBindings; private bool _canApplyToLayer; private bool _canSelectEditTool; private BindableCollection _devices; private BindableCollection _highlightedLeds; private PluginSetting _highlightSelectedLayer; - private PluginSetting _alwaysApplyDataBindings; + private DateTime _lastUpdate; private PanZoomViewModel _panZoomViewModel; private Layer _previousSelectedLayer; private int _previousTool; - private DateTime _lastUpdate; public ProfileViewModel(IProfileEditorService profileEditorService, + IRgbService rgbService, ICoreService coreService, - ISurfaceService surfaceService, ISettingsService settingsService, IEventAggregator eventAggregator, IVisualizationToolVmFactory visualizationToolVmFactory, IProfileLayerVmFactory profileLayerVmFactory) { _profileEditorService = profileEditorService; + _rgbService = rgbService; _coreService = coreService; - _surfaceService = surfaceService; _settingsService = settingsService; _visualizationToolVmFactory = visualizationToolVmFactory; _profileLayerVmFactory = profileLayerVmFactory; @@ -99,25 +99,21 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization { // Remove the tool from the canvas if (_activeToolViewModel != null) - { lock (Items) { Items.Remove(_activeToolViewModel); NotifyOfPropertyChange(() => Items); } - } // Set the new tool SetAndNotify(ref _activeToolViewModel, value); // Add the new tool to the canvas if (_activeToolViewModel != null) - { lock (Items) { Items.Add(_activeToolViewModel); NotifyOfPropertyChange(() => Items); } - } } } @@ -144,7 +140,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization Devices = new BindableCollection(); HighlightedLeds = new BindableCollection(); - ApplySurfaceConfiguration(_surfaceService.ActiveSurface); + ApplyDevices(); ActivateToolByIndex(0); ApplyActiveProfile(); @@ -156,7 +152,8 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization _coreService.FrameRendered += OnFrameRendered; HighlightSelectedLayer.SettingChanged += HighlightSelectedLayerOnSettingChanged; - _surfaceService.ActiveSurfaceConfigurationSelected += OnActiveSurfaceConfigurationSelected; + _rgbService.DeviceAdded += RgbServiceOnDevicesModified; + _rgbService.DeviceRemoved += RgbServiceOnDevicesModified; _profileEditorService.ProfileSelected += OnProfileSelected; _profileEditorService.ProfileElementSelected += OnProfileElementSelected; _profileEditorService.SelectedProfileElementUpdated += OnSelectedProfileElementUpdated; @@ -168,7 +165,8 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization { _coreService.FrameRendered -= OnFrameRendered; HighlightSelectedLayer.SettingChanged -= HighlightSelectedLayerOnSettingChanged; - _surfaceService.ActiveSurfaceConfigurationSelected -= OnActiveSurfaceConfigurationSelected; + _rgbService.DeviceAdded -= RgbServiceOnDevicesModified; + _rgbService.DeviceRemoved -= RgbServiceOnDevicesModified; _profileEditorService.ProfileSelected -= OnProfileSelected; _profileEditorService.ProfileElementSelected -= OnProfileElementSelected; _profileEditorService.SelectedProfileElementUpdated -= OnSelectedProfileElementUpdated; @@ -181,9 +179,9 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization base.OnClose(); } - private void OnActiveSurfaceConfigurationSelected(object sender, SurfaceConfigurationEventArgs e) + private void RgbServiceOnDevicesModified(object? sender, DeviceEventArgs e) { - ApplySurfaceConfiguration(e.Surface); + ApplyDevices(); } private void ApplyActiveProfile() @@ -193,10 +191,8 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization // Add new layers missing a VM foreach (Layer layer in layers) - { if (layerViewModels.All(vm => vm.Layer != layer)) Items.Add(_profileLayerVmFactory.Create(layer, PanZoomViewModel)); - } // Remove layers that no longer exist IEnumerable toRemove = layerViewModels.Where(vm => !layers.Contains(vm.Layer)); @@ -204,10 +200,10 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization Items.Remove(profileLayerViewModel); } - private void ApplySurfaceConfiguration(ArtemisSurface surface) + private void ApplyDevices() { Devices.Clear(); - Devices.AddRange(surface.Devices.Where(d => d.IsEnabled).OrderBy(d => d.ZIndex)); + Devices.AddRange(_rgbService.EnabledDevices.Where(d => d.IsEnabled).OrderBy(d => d.ZIndex)); } private void UpdateLedsDimStatus() @@ -259,7 +255,15 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization public void ResetZoomAndPan() { - PanZoomViewModel.Reset(); + // Create a rect surrounding all devices + SKRect rect = new SKRect( + Devices.Min(d => d.Rectangle.Left), + Devices.Min(d => d.Rectangle.Top), + Devices.Max(d => d.Rectangle.Right), + Devices.Max(d => d.Rectangle.Bottom) + ); + + PanZoomViewModel.Reset(rect); } #endregion @@ -339,7 +343,9 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization _previousSelectedLayer.Transform.LayerPropertyOnCurrentValueSet += TransformValueChanged; } else + { _previousSelectedLayer = null; + } ApplyActiveProfile(); UpdateLedsDimStatus(); diff --git a/src/Artemis.UI/Screens/Settings/Debug/DeviceDebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/DeviceDebugView.xaml index b870db7a1..ba1a04987 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/DeviceDebugView.xaml +++ b/src/Artemis.UI/Screens/Settings/Debug/DeviceDebugView.xaml @@ -42,6 +42,7 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - +