diff --git a/src/Artemis.Core/Events/Plugins/DeviceProviderEventArgs.cs b/src/Artemis.Core/Events/Plugins/DeviceProviderEventArgs.cs deleted file mode 100644 index 878946530..000000000 --- a/src/Artemis.Core/Events/Plugins/DeviceProviderEventArgs.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using Artemis.Core.DeviceProviders; - -namespace Artemis.Core; - -/// -/// Provides data about device provider related events -/// -public class DeviceProviderEventArgs : EventArgs -{ - internal DeviceProviderEventArgs(DeviceProvider deviceProvider, List devices) - { - DeviceProvider = deviceProvider; - Devices = devices; - } - - /// - /// Gets the device provider the event is related to. - /// - public DeviceProvider DeviceProvider { get; } - - /// - /// Gets a list of the affected devices. - /// - public List Devices { get; set; } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/ProfileConfiguration/Hotkey.cs b/src/Artemis.Core/Models/ProfileConfiguration/Hotkey.cs index b7dc91525..ac2c9ed24 100644 --- a/src/Artemis.Core/Models/ProfileConfiguration/Hotkey.cs +++ b/src/Artemis.Core/Models/ProfileConfiguration/Hotkey.cs @@ -1,5 +1,4 @@ -using System; -using Artemis.Core.Services; +using Artemis.Core.Services; using Artemis.Storage.Entities.Profile; namespace Artemis.Core; @@ -17,14 +16,6 @@ public class Hotkey : CorePropertyChanged, IStorageModel Entity = new ProfileConfigurationHotkeyEntity(); } - /// - public Hotkey(KeyboardKey? key, KeyboardModifierKey? modifiers) - { - Key = key; - Modifiers = modifiers; - Entity = new ProfileConfigurationHotkeyEntity(); - } - /// /// Creates a new instance of based on the provided entity /// @@ -55,7 +46,7 @@ public class Hotkey : CorePropertyChanged, IStorageModel /// if the event args match the hotkey; otherwise public bool MatchesEventArgs(ArtemisKeyboardKeyEventArgs eventArgs) { - return eventArgs.Key == Key && (eventArgs.Modifiers == Modifiers || (eventArgs.Modifiers == KeyboardModifierKey.None && Modifiers == null)); + return eventArgs.Key == Key && eventArgs.Modifiers == Modifiers; } #region Implementation of IStorageModel diff --git a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs index ef29c01f7..bf1e6211b 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs @@ -15,17 +15,11 @@ namespace Artemis.Core; /// public class ArtemisDevice : CorePropertyChanged { - private readonly List _originalLeds; - private readonly Size _originalSize; private SKPath? _path; private SKRect _rectangle; internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider) { - _originalLeds = new List(rgbDevice.Select(l => new OriginalLed(l))); - Rectangle ledRectangle = new(rgbDevice.Select(x => x.Boundary)); - _originalSize = ledRectangle.Size + new Size(ledRectangle.Location.X, ledRectangle.Location.Y); - Identifier = rgbDevice.GetDeviceIdentifier(); DeviceEntity = new DeviceEntity(); RgbDevice = rgbDevice; @@ -54,10 +48,6 @@ public class ArtemisDevice : CorePropertyChanged internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider, DeviceEntity deviceEntity) { - _originalLeds = new List(rgbDevice.Select(l => new OriginalLed(l))); - Rectangle ledRectangle = new(rgbDevice.Select(x => x.Boundary)); - _originalSize = ledRectangle.Size + new Size(ledRectangle.Location.X, ledRectangle.Location.Y); - Identifier = rgbDevice.GetDeviceIdentifier(); DeviceEntity = deviceEntity; RgbDevice = rgbDevice; @@ -360,40 +350,6 @@ public class ArtemisDevice : CorePropertyChanged return artemisLed; } - /// - /// Returns the most preferred device layout for this device. - /// - /// The most preferred device layout for this device. - public ArtemisLayout? GetBestDeviceLayout() - { - ArtemisLayout? layout; - - // Configured layout path takes precedence over all other options - if (CustomLayoutPath != null) - { - layout = new ArtemisLayout(CustomLayoutPath, LayoutSource.Configured); - if (layout.IsValid) - return layout; - } - - // Look for a layout provided by the user - layout = DeviceProvider.LoadUserLayout(this); - if (layout.IsValid) - return layout; - - if (DisableDefaultLayout) - return null; - - // Look for a layout provided by the plugin - layout = DeviceProvider.LoadLayout(this); - if (layout.IsValid) - return layout; - - // Finally fall back to a default layout - layout = ArtemisLayout.GetDefaultLayout(this); - return layout; - } - /// /// Occurs when the underlying RGB.NET device was updated /// @@ -459,18 +415,8 @@ public class ArtemisDevice : CorePropertyChanged /// A boolean indicating whether to remove excess LEDs present in the device but missing /// in the layout /// - internal void ApplyLayout(ArtemisLayout? layout, bool createMissingLeds, bool removeExcessiveLeds) + internal void ApplyLayout(ArtemisLayout layout, bool createMissingLeds, bool removeExcessiveLeds) { - if (layout == null) - { - ClearLayout(); - UpdateLeds(); - - CalculateRenderProperties(); - OnDeviceUpdated(); - return; - } - if (createMissingLeds && !DeviceProvider.CreateMissingLedsSupported) throw new ArtemisCoreException($"Cannot apply layout with {nameof(createMissingLeds)} " + "set to true because the device provider does not support it"); @@ -478,33 +424,18 @@ public class ArtemisDevice : CorePropertyChanged throw new ArtemisCoreException($"Cannot apply layout with {nameof(removeExcessiveLeds)} " + "set to true because the device provider does not support it"); - ClearLayout(); if (layout.IsValid) layout.ApplyTo(RgbDevice, createMissingLeds, removeExcessiveLeds); + + UpdateLeds(); - + Layout = layout; Layout.ApplyDevice(this); - CalculateRenderProperties(); OnDeviceUpdated(); } - private void ClearLayout() - { - if (Layout == null) - return; - - RgbDevice.DeviceInfo.LayoutMetadata = null; - RgbDevice.Size = _originalSize; - Layout = null; - - while (RgbDevice.Any()) - RgbDevice.RemoveLed(RgbDevice.First().Id); - foreach (OriginalLed originalLed in _originalLeds) - RgbDevice.AddLed(originalLed.Id, originalLed.Location, originalLed.Size, originalLed.CustomData); - } - internal void ApplyToEntity() { // Other properties are computed diff --git a/src/Artemis.Core/Models/Surface/ArtemisLed.cs b/src/Artemis.Core/Models/Surface/ArtemisLed.cs index f3d0500e1..48d1be479 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisLed.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisLed.cs @@ -59,13 +59,13 @@ public class ArtemisLed : CorePropertyChanged internal void CalculateRectangles() { - Rectangle = RenderScale.CreateScaleCompatibleRect( + Rectangle = Utilities.CreateScaleCompatibleRect( RgbLed.Boundary.Location.X, RgbLed.Boundary.Location.Y, RgbLed.Boundary.Size.Width, RgbLed.Boundary.Size.Height ); - AbsoluteRectangle = RenderScale.CreateScaleCompatibleRect( + AbsoluteRectangle = Utilities.CreateScaleCompatibleRect( RgbLed.AbsoluteBoundary.Location.X, RgbLed.AbsoluteBoundary.Location.Y, RgbLed.AbsoluteBoundary.Size.Width, diff --git a/src/Artemis.Core/Models/Surface/OriginalLed.cs b/src/Artemis.Core/Models/Surface/OriginalLed.cs deleted file mode 100644 index 87e3f4835..000000000 --- a/src/Artemis.Core/Models/Surface/OriginalLed.cs +++ /dev/null @@ -1,19 +0,0 @@ -using RGB.NET.Core; - -namespace Artemis.Core; - -internal class OriginalLed -{ - public OriginalLed(Led source) - { - Id = source.Id; - Location = source.Location; - Size = source.Size; - CustomData = source.CustomData; - } - - public LedId Id { get; set; } - public Point Location { get; set; } - public Size Size { get; set; } - public object? CustomData { get; set; } -} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Core/IRenderer.cs b/src/Artemis.Core/Services/Core/IRenderer.cs deleted file mode 100644 index 8aac001db..000000000 --- a/src/Artemis.Core/Services/Core/IRenderer.cs +++ /dev/null @@ -1,23 +0,0 @@ -using SkiaSharp; - -namespace Artemis.Core.Services.Core; - -/// -/// Represents a renderer that renders to a canvas. -/// -public interface IRenderer -{ - /// - /// Renders to the provided canvas, the delta is the time in seconds since the last time was - /// called. - /// - /// The canvas to render to. - /// The time in seconds since the last time was called. - void Render(SKCanvas canvas, double delta); - - /// - /// Called after the rendering has taken place. - /// - /// The texture that the render resulted in. - void PostRender(SKTexture texture); -} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Core/SurfaceManager.cs b/src/Artemis.Core/Services/Core/SurfaceManager.cs deleted file mode 100644 index fb3845e3c..000000000 --- a/src/Artemis.Core/Services/Core/SurfaceManager.cs +++ /dev/null @@ -1,222 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Artemis.Core.SkiaSharp; -using RGB.NET.Core; -using SkiaSharp; - -namespace Artemis.Core.Services.Core; - -/// -/// An engine drivers an update loop for a set of devices using a graphics context -/// -internal sealed class SurfaceManager : IDisposable -{ - private readonly IRenderer _renderer; - private readonly TimerUpdateTrigger _updateTrigger; - private readonly object _renderLock = new(); - private readonly List _devices = new(); - private readonly SKTextureBrush _textureBrush = new(null) {CalculationMode = RenderMode.Absolute}; - - private ListLedGroup? _surfaceLedGroup; - private SKTexture? _texture; - - public SurfaceManager(IRenderer renderer, IManagedGraphicsContext? graphicsContext, int targetFrameRate, float renderScale) - { - _renderer = renderer; - _updateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / targetFrameRate}; - - GraphicsContext = graphicsContext; - TargetFrameRate = targetFrameRate; - RenderScale = renderScale; - Surface = new RGBSurface(); - Surface.Updating += SurfaceOnUpdating; - Surface.RegisterUpdateTrigger(_updateTrigger); - - SetPaused(true); - } - - private void SurfaceOnUpdating(UpdatingEventArgs args) - { - lock (_renderLock) - { - SKTexture? texture = _texture; - if (texture == null || texture.IsInvalid) - texture = CreateTexture(); - - // Prepare a canvas - SKCanvas canvas = texture.Surface.Canvas; - canvas.Save(); - - // Apply scaling if necessary - if (Math.Abs(texture.RenderScale - 1) > 0.001) - canvas.Scale(texture.RenderScale); - - // Fresh start! - canvas.Clear(new SKColor(0, 0, 0)); - - try - { - _renderer.Render(canvas, args.DeltaTime); - } - finally - { - canvas.RestoreToCount(-1); - canvas.Flush(); - texture.CopyPixelData(); - } - - try - { - _renderer.PostRender(texture); - } - catch - { - // ignored - } - } - } - - public IManagedGraphicsContext? GraphicsContext { get; private set; } - public int TargetFrameRate { get; private set; } - public float RenderScale { get; private set; } - public RGBSurface Surface { get; } - - public bool IsPaused { get; private set; } - - public void AddDevices(IEnumerable devices) - { - lock (_renderLock) - { - foreach (ArtemisDevice artemisDevice in devices) - { - if (_devices.Contains(artemisDevice)) - continue; - _devices.Add(artemisDevice); - Surface.Attach(artemisDevice.RgbDevice); - artemisDevice.DeviceUpdated += ArtemisDeviceOnDeviceUpdated; - } - - Update(); - } - } - - public void RemoveDevices(IEnumerable devices) - { - lock (_renderLock) - { - foreach (ArtemisDevice artemisDevice in devices) - { - artemisDevice.DeviceUpdated -= ArtemisDeviceOnDeviceUpdated; - Surface.Detach(artemisDevice.RgbDevice); - _devices.Remove(artemisDevice); - } - - Update(); - } - } - - public bool SetPaused(bool paused) - { - if (IsPaused == paused) - return false; - - if (paused) - _updateTrigger.Stop(); - else - _updateTrigger.Start(); - - IsPaused = paused; - return true; - } - - private void Update() - { - lock (_renderLock) - { - UpdateLedGroup(); - CreateTexture(); - } - } - - private void UpdateLedGroup() - { - List leds = _devices.SelectMany(d => d.Leds).Select(l => l.RgbLed).ToList(); - - if (_surfaceLedGroup == null) - { - _surfaceLedGroup = new ListLedGroup(Surface, leds) {Brush = _textureBrush}; - LedsChanged?.Invoke(this, EventArgs.Empty); - return; - } - - // Clean up the old background - _surfaceLedGroup.Detach(); - - // Apply the application wide brush and decorator - _surfaceLedGroup = new ListLedGroup(Surface, leds) {Brush = _textureBrush}; - LedsChanged?.Invoke(this, EventArgs.Empty); - } - - private SKTexture CreateTexture() - { - float evenWidth = Surface.Boundary.Size.Width; - if (evenWidth % 2 != 0) - evenWidth++; - float evenHeight = Surface.Boundary.Size.Height; - if (evenHeight % 2 != 0) - evenHeight++; - - int width = Math.Max(1, MathF.Min(evenWidth * RenderScale, 4096).RoundToInt()); - int height = Math.Max(1, MathF.Min(evenHeight * RenderScale, 4096).RoundToInt()); - - _texture?.Dispose(); - _texture = new SKTexture(GraphicsContext, width, height, RenderScale, _devices); - _textureBrush.Texture = _texture; - - return _texture; - } - - private void ArtemisDeviceOnDeviceUpdated(object? sender, EventArgs e) - { - Update(); - } - - public event EventHandler? LedsChanged; - - /// - public void Dispose() - { - SetPaused(true); - - Surface.UnregisterUpdateTrigger(_updateTrigger); - _updateTrigger.Dispose(); - _texture?.Dispose(); - Surface.Dispose(); - } - - public void UpdateTargetFrameRate(int targetFrameRate) - { - TargetFrameRate = targetFrameRate; - _updateTrigger.UpdateFrequency = 1.0 / TargetFrameRate; - } - - public void UpdateRenderScale(float renderScale) - { - lock (_renderLock) - { - RenderScale = renderScale; - _texture?.Invalidate(); - } - } - - public void UpdateGraphicsContext(IManagedGraphicsContext? graphicsContext) - { - lock (_renderLock) - { - GraphicsContext = graphicsContext; - _texture?.Dispose(); - _texture = null; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 89a951149..137f339b8 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -20,11 +20,19 @@ namespace Artemis.Core.Services; /// internal class CoreService : ICoreService { + private readonly Stopwatch _frameStopWatch; private readonly ILogger _logger; private readonly PluginSetting _loggingLevel; + private readonly IModuleService _moduleService; private readonly IPluginManagementService _pluginManagementService; - private readonly IRenderService _renderService; + private readonly IProfileService _profileService; + private readonly IRgbService _rgbService; + private readonly IScriptingService _scriptingService; + private readonly List _updateExceptions = new(); + private int _frames; + private DateTime _lastExceptionLog; + private DateTime _lastFrameRateSample; // ReSharper disable UnusedParameter.Local public CoreService(IContainer container, @@ -32,49 +40,36 @@ internal class CoreService : ICoreService StorageMigrationService _1, // injected to ensure migration runs early ISettingsService settingsService, IPluginManagementService pluginManagementService, + IRgbService rgbService, IProfileService profileService, IModuleService moduleService, - IScriptingService scriptingService, - IRenderService renderService) + IScriptingService scriptingService) { Constants.CorePlugin.Container = container; _logger = logger; _pluginManagementService = pluginManagementService; - _renderService = renderService; + _rgbService = rgbService; + _profileService = profileService; + _moduleService = moduleService; + _scriptingService = scriptingService; _loggingLevel = settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Debug); + _frameStopWatch = new Stopwatch(); + + _rgbService.Surface.Updating += SurfaceOnUpdating; _loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel(); } - - public bool IsElevated { get; set; } - public bool IsInitialized { get; set; } + // ReSharper restore UnusedParameter.Local - public void Initialize() + protected virtual void OnFrameRendering(FrameRenderingEventArgs e) { - if (IsInitialized) - throw new ArtemisCoreException("Cannot initialize the core as it is already initialized."); + FrameRendering?.Invoke(this, e); + } - _logger.Information("Initializing Artemis Core version {CurrentVersion}", Constants.CurrentVersion); - _logger.Information("Startup arguments: {StartupArguments}", Constants.StartupArguments); - _logger.Information("Elevated permissions: {IsElevated}", IsElevated); - _logger.Information("Stopwatch high resolution: {IsHighResolution}", Stopwatch.IsHighResolution); - - ApplyLoggingLevel(); - - ProcessMonitor.Start(); - - // Don't remove even if it looks useless - // Just this line should prevent a certain someone from removing HidSharp as an unused dependency as well - Version? hidSharpVersion = Assembly.GetAssembly(typeof(HidDevice))!.GetName().Version; - _logger.Debug("Forcing plugins to use HidSharp {HidSharpVersion}", hidSharpVersion); - - // Initialize the services - _pluginManagementService.CopyBuiltInPlugins(); - _pluginManagementService.LoadPlugins(IsElevated); - _renderService.Initialize(); - - OnInitialized(); + protected virtual void OnFrameRendered(FrameRenderedEventArgs e) + { + FrameRendered?.Invoke(this, e); } private void ApplyLoggingLevel() @@ -103,11 +98,131 @@ internal class CoreService : ICoreService } } - public event EventHandler? Initialized; + private void SurfaceOnUpdating(UpdatingEventArgs args) + { + if (_rgbService.IsRenderPaused) + return; + + if (_rgbService.FlushLeds) + { + _rgbService.FlushLeds = false; + _rgbService.Surface.Update(true); + return; + } + + try + { + _frameStopWatch.Restart(); + + foreach (GlobalScript script in _scriptingService.GlobalScripts) + script.OnCoreUpdating(args.DeltaTime); + + _moduleService.UpdateActiveModules(args.DeltaTime); + SKTexture texture = _rgbService.OpenRender(); + SKCanvas canvas = texture.Surface.Canvas; + canvas.Save(); + if (Math.Abs(texture.RenderScale - 1) > 0.001) + canvas.Scale(texture.RenderScale); + canvas.Clear(new SKColor(0, 0, 0)); + + if (!ProfileRenderingDisabled) + { + _profileService.UpdateProfiles(args.DeltaTime); + _profileService.RenderProfiles(canvas); + } + + OnFrameRendering(new FrameRenderingEventArgs(canvas, args.DeltaTime, _rgbService.Surface)); + canvas.RestoreToCount(-1); + canvas.Flush(); + + OnFrameRendered(new FrameRenderedEventArgs(texture, _rgbService.Surface)); + + foreach (GlobalScript script in _scriptingService.GlobalScripts) + script.OnCoreUpdated(args.DeltaTime); + } + catch (Exception e) + { + _updateExceptions.Add(e); + } + finally + { + _rgbService.CloseRender(); + _frameStopWatch.Stop(); + _frames++; + + if ((DateTime.Now - _lastFrameRateSample).TotalSeconds >= 1) + { + FrameRate = _frames; + _frames = 0; + _lastFrameRateSample = DateTime.Now; + } + + FrameTime = _frameStopWatch.Elapsed; + + LogUpdateExceptions(); + } + } + + private void LogUpdateExceptions() + { + // Only log update exceptions every 10 seconds to avoid spamming the logs + if (DateTime.Now - _lastExceptionLog < TimeSpan.FromSeconds(10)) + return; + _lastExceptionLog = DateTime.Now; + + if (!_updateExceptions.Any()) + return; + + // Group by stack trace, that should gather up duplicate exceptions + foreach (IGrouping exceptions in _updateExceptions.GroupBy(e => e.StackTrace)) + _logger.Warning(exceptions.First(), "Exception was thrown {count} times during update in the last 10 seconds", exceptions.Count()); + + // When logging is finished start with a fresh slate + _updateExceptions.Clear(); + } private void OnInitialized() { IsInitialized = true; Initialized?.Invoke(this, EventArgs.Empty); } + + public int FrameRate { get; private set; } + public TimeSpan FrameTime { get; private set; } + public bool ProfileRenderingDisabled { get; set; } + public bool IsElevated { get; set; } + + public bool IsInitialized { get; set; } + + public void Initialize() + { + if (IsInitialized) + throw new ArtemisCoreException("Cannot initialize the core as it is already initialized."); + + _logger.Information("Initializing Artemis Core version {CurrentVersion}", Constants.CurrentVersion); + _logger.Information("Startup arguments: {StartupArguments}", Constants.StartupArguments); + _logger.Information("Elevated permissions: {IsElevated}", IsElevated); + _logger.Information("Stopwatch high resolution: {IsHighResolution}", Stopwatch.IsHighResolution); + + ApplyLoggingLevel(); + + ProcessMonitor.Start(); + + // Don't remove even if it looks useless + // Just this line should prevent a certain someone from removing HidSharp as an unused dependency as well + Version? hidSharpVersion = Assembly.GetAssembly(typeof(HidDevice))!.GetName().Version; + _logger.Debug("Forcing plugins to use HidSharp {HidSharpVersion}", hidSharpVersion); + + // Initialize the services + _pluginManagementService.CopyBuiltInPlugins(); + _pluginManagementService.LoadPlugins(IsElevated); + + _rgbService.ApplyPreferredGraphicsContext(Constants.StartupArguments.Contains("--force-software-render")); + _rgbService.SetRenderPaused(false); + OnInitialized(); + } + + public event EventHandler? Initialized; + public event EventHandler? FrameRendering; + public event EventHandler? FrameRendered; } \ No newline at end of file diff --git a/src/Artemis.Core/Services/DeviceService.cs b/src/Artemis.Core/Services/DeviceService.cs index 2cddc24f4..f0b170289 100644 --- a/src/Artemis.Core/Services/DeviceService.cs +++ b/src/Artemis.Core/Services/DeviceService.cs @@ -1,252 +1,22 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; +using System.Linq; using System.Threading.Tasks; -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 DeviceService : IDeviceService { - private readonly ILogger _logger; - private readonly IPluginManagementService _pluginManagementService; - private readonly IDeviceRepository _deviceRepository; - private readonly Lazy _renderService; - private readonly List _enabledDevices = new(); - private readonly List _devices = new(); + private readonly IRgbService _rgbService; - public DeviceService(ILogger logger, IPluginManagementService pluginManagementService, IDeviceRepository deviceRepository, Lazy renderService) + public DeviceService(IRgbService rgbService) { - _logger = logger; - _pluginManagementService = pluginManagementService; - _deviceRepository = deviceRepository; - _renderService = renderService; - - EnabledDevices = new ReadOnlyCollection(_enabledDevices); - Devices = new ReadOnlyCollection(_devices); - - RenderScale.RenderScaleMultiplierChanged += RenderScaleOnRenderScaleMultiplierChanged; + _rgbService = rgbService; } - public IReadOnlyCollection EnabledDevices { get; } - public IReadOnlyCollection Devices { get; } - - /// - public void IdentifyDevice(ArtemisDevice device) - { - BlinkDevice(device, 0); - } - - /// - public void AddDeviceProvider(DeviceProvider deviceProvider) - { - _logger.Verbose("[AddDeviceProvider] Adding {DeviceProvider}", deviceProvider.GetType().Name); - IRGBDeviceProvider rgbDeviceProvider = deviceProvider.RgbDeviceProvider; - - try - { - // Can't see why this would happen, RgbService used to do this though - List toRemove = _devices.Where(a => rgbDeviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList(); - _logger.Verbose("[AddDeviceProvider] Removing {Count} old device(s)", toRemove.Count); - foreach (ArtemisDevice device in toRemove) - { - _devices.Remove(device); - OnDeviceRemoved(new DeviceEventArgs(device)); - } - - List providerExceptions = new(); - - void DeviceProviderOnException(object? sender, ExceptionEventArgs e) - { - if (e.IsCritical) - providerExceptions.Add(e.Exception); - else - _logger.Warning(e.Exception, "Device provider {deviceProvider} threw non-critical exception", deviceProvider.GetType().Name); - } - - _logger.Verbose("[AddDeviceProvider] Initializing device provider"); - rgbDeviceProvider.Exception += DeviceProviderOnException; - rgbDeviceProvider.Initialize(); - _logger.Verbose("[AddDeviceProvider] Attaching devices of device provider"); - rgbDeviceProvider.Exception -= DeviceProviderOnException; - if (providerExceptions.Count == 1) - throw new ArtemisPluginException("RGB.NET threw exception: " + providerExceptions.First().Message, providerExceptions.First()); - if (providerExceptions.Count > 1) - throw new ArtemisPluginException("RGB.NET threw multiple exceptions", new AggregateException(providerExceptions)); - - if (!rgbDeviceProvider.Devices.Any()) - { - _logger.Warning("Device provider {deviceProvider} has no devices", deviceProvider.GetType().Name); - return; - } - - List addedDevices = new(); - foreach (IRGBDevice rgbDevice in rgbDeviceProvider.Devices) - { - ArtemisDevice artemisDevice = GetArtemisDevice(rgbDevice); - addedDevices.Add(artemisDevice); - _devices.Add(artemisDevice); - if (artemisDevice.IsEnabled) - _enabledDevices.Add(artemisDevice); - - _logger.Debug("Device provider {deviceProvider} added {deviceName}", deviceProvider.GetType().Name, rgbDevice.DeviceInfo.DeviceName); - } - - _devices.Sort((a, b) => a.ZIndex - b.ZIndex); - _enabledDevices.Sort((a, b) => a.ZIndex - b.ZIndex); - - OnDeviceProviderAdded(new DeviceProviderEventArgs(deviceProvider, addedDevices)); - foreach (ArtemisDevice artemisDevice in addedDevices) - OnDeviceAdded(new DeviceEventArgs(artemisDevice)); - - UpdateLeds(); - } - catch (Exception e) - { - _logger.Error(e, "Exception during device loading for device provider {deviceProvider}", deviceProvider.GetType().Name); - throw; - } - } - - /// - public void RemoveDeviceProvider(DeviceProvider deviceProvider) - { - _logger.Verbose("[RemoveDeviceProvider] Pausing rendering to remove {DeviceProvider}", deviceProvider.GetType().Name); - IRGBDeviceProvider rgbDeviceProvider = deviceProvider.RgbDeviceProvider; - List toRemove = _devices.Where(a => rgbDeviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList(); - - try - { - _logger.Verbose("[RemoveDeviceProvider] Removing {Count} old device(s)", toRemove.Count); - foreach (ArtemisDevice device in toRemove) - { - _devices.Remove(device); - _enabledDevices.Remove(device); - } - - _devices.Sort((a, b) => a.ZIndex - b.ZIndex); - - OnDeviceProviderRemoved(new DeviceProviderEventArgs(deviceProvider, toRemove)); - foreach (ArtemisDevice artemisDevice in toRemove) - OnDeviceRemoved(new DeviceEventArgs(artemisDevice)); - - UpdateLeds(); - } - catch (Exception e) - { - _logger.Error(e, "Exception during device removal for device provider {deviceProvider}", deviceProvider.GetType().Name); - throw; - } - } - - /// - public void AutoArrangeDevices() - { - SurfaceArrangement surfaceArrangement = SurfaceArrangement.GetDefaultArrangement(); - surfaceArrangement.Arrange(_devices); - foreach (ArtemisDevice artemisDevice in _devices) - artemisDevice.ApplyDefaultCategories(); - - SaveDevices(); - } - - /// - public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout? layout) - { - if (layout == null || layout.Source == LayoutSource.Default) - device.ApplyLayout(layout, false, false); - else - device.ApplyLayout(layout, device.DeviceProvider.CreateMissingLedsSupported, device.DeviceProvider.RemoveExcessiveLedsSupported); - - UpdateLeds(); - } - - /// - public void EnableDevice(ArtemisDevice device) - { - if (device.IsEnabled) - return; - - _enabledDevices.Add(device); - device.IsEnabled = true; - device.ApplyToEntity(); - _deviceRepository.Save(device.DeviceEntity); - - OnDeviceEnabled(new DeviceEventArgs(device)); - UpdateLeds(); - } - - /// - public void DisableDevice(ArtemisDevice device) - { - if (!device.IsEnabled) - return; - - _enabledDevices.Remove(device); - device.IsEnabled = false; - device.ApplyToEntity(); - _deviceRepository.Save(device.DeviceEntity); - - OnDeviceDisabled(new DeviceEventArgs(device)); - UpdateLeds(); - } - - /// - public void SaveDevice(ArtemisDevice artemisDevice) - { - artemisDevice.ApplyToEntity(); - artemisDevice.ApplyToRgbDevice(); - - _deviceRepository.Save(artemisDevice.DeviceEntity); - UpdateLeds(); - } - - /// - public void SaveDevices() - { - foreach (ArtemisDevice artemisDevice in _devices) - { - artemisDevice.ApplyToEntity(); - artemisDevice.ApplyToRgbDevice(); - } - - _deviceRepository.Save(_devices.Select(d => d.DeviceEntity)); - UpdateLeds(); - } - - private ArtemisDevice GetArtemisDevice(IRGBDevice rgbDevice) - { - string deviceIdentifier = rgbDevice.GetDeviceIdentifier(); - DeviceEntity? deviceEntity = _deviceRepository.Get(deviceIdentifier); - DeviceProvider deviceProvider = _pluginManagementService.GetDeviceProviderByDevice(rgbDevice); - - ArtemisDevice device; - if (deviceEntity != null) - device = new ArtemisDevice(rgbDevice, deviceProvider, deviceEntity); - // 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); - device = new ArtemisDevice(rgbDevice, deviceProvider); - } - - device.ApplyToRgbDevice(); - ApplyDeviceLayout(device, device.GetBestDeviceLayout()); - return device; - } - private void BlinkDevice(ArtemisDevice device, int blinkCount) { - RGBSurface surface = _renderService.Value.Surface; - // Create a LED group way at the top - ListLedGroup ledGroup = new(surface, device.Leds.Select(l => l.RgbLed)) + ListLedGroup ledGroup = new(_rgbService.Surface, device.Leds.Select(l => l.RgbLed)) { Brush = new SolidColorBrush(new Color(255, 255, 255)), ZIndex = 999 @@ -266,81 +36,9 @@ internal class DeviceService : IDeviceService } }); } - - private void CalculateRenderProperties() + + public void IdentifyDevice(ArtemisDevice device) { - foreach (ArtemisDevice artemisDevice in Devices) - artemisDevice.CalculateRenderProperties(); - UpdateLeds(); + BlinkDevice(device, 0); } - - private void UpdateLeds() - { - OnLedsChanged(); - } - - private void RenderScaleOnRenderScaleMultiplierChanged(object? sender, EventArgs e) - { - CalculateRenderProperties(); - } - - #region Events - - /// - public event EventHandler? DeviceAdded; - - /// - public event EventHandler? DeviceRemoved; - - /// - public event EventHandler? DeviceEnabled; - - /// - public event EventHandler? DeviceDisabled; - - /// - public event EventHandler? DeviceProviderAdded; - - /// - public event EventHandler? DeviceProviderRemoved; - - /// - public event EventHandler? LedsChanged; - - protected virtual void OnDeviceAdded(DeviceEventArgs e) - { - DeviceAdded?.Invoke(this, e); - } - - protected virtual void OnDeviceRemoved(DeviceEventArgs e) - { - DeviceRemoved?.Invoke(this, e); - } - - protected virtual void OnDeviceEnabled(DeviceEventArgs e) - { - DeviceEnabled?.Invoke(this, e); - } - - protected virtual void OnDeviceDisabled(DeviceEventArgs e) - { - DeviceDisabled?.Invoke(this, e); - } - - protected virtual void OnDeviceProviderAdded(DeviceProviderEventArgs e) - { - DeviceProviderAdded?.Invoke(this, e); - } - - protected virtual void OnDeviceProviderRemoved(DeviceProviderEventArgs e) - { - DeviceProviderRemoved?.Invoke(this, e); - } - - protected virtual void OnLedsChanged() - { - LedsChanged?.Invoke(this, EventArgs.Empty); - } - - #endregion } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Input/Events/ArtemisKeyboardKeyEventArgs.cs b/src/Artemis.Core/Services/Input/Events/ArtemisKeyboardKeyEventArgs.cs index 4738bab71..e6193c515 100644 --- a/src/Artemis.Core/Services/Input/Events/ArtemisKeyboardKeyEventArgs.cs +++ b/src/Artemis.Core/Services/Input/Events/ArtemisKeyboardKeyEventArgs.cs @@ -34,13 +34,4 @@ public class ArtemisKeyboardKeyEventArgs : EventArgs /// Gets the modifiers that are pressed /// public KeyboardModifierKey Modifiers { get; } - - /// - /// Creates a hotkey matching the event. - /// - /// The resulting hotkey. - public Hotkey ToHotkey() - { - return new Hotkey {Key = Key, Modifiers = Modifiers}; - } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Input/InputService.cs b/src/Artemis.Core/Services/Input/InputService.cs index 179c6641d..a7502c6ef 100644 --- a/src/Artemis.Core/Services/Input/InputService.cs +++ b/src/Artemis.Core/Services/Input/InputService.cs @@ -9,19 +9,19 @@ namespace Artemis.Core.Services; internal class InputService : IInputService { private readonly ILogger _logger; - private readonly IDeviceService _deviceService; + private readonly IRgbService _rgbService; private ArtemisDevice? _firstKeyboard; private ArtemisDevice? _firstMouse; private int _keyboardCount; private int _mouseCount; - public InputService(ILogger logger, IDeviceService deviceService) + public InputService(ILogger logger, IRgbService rgbService) { _logger = logger; - _deviceService = deviceService; + _rgbService = rgbService; - _deviceService.DeviceAdded += DeviceServiceOnDevicesModified; - _deviceService.DeviceRemoved += DeviceServiceOnDevicesModified; + _rgbService.DeviceAdded += RgbServiceOnDevicesModified; + _rgbService.DeviceRemoved += RgbServiceOnDevicesModified; BustIdentifierCache(); } @@ -157,7 +157,7 @@ internal class InputService : IInputService _logger.Debug("Stop identifying device {device}", _identifyingDevice); _identifyingDevice = null; - _deviceService.SaveDevices(); + _rgbService.SaveDevices(); BustIdentifierCache(); } @@ -209,7 +209,7 @@ internal class InputService : IInputService public void BustIdentifierCache() { _deviceCache.Clear(); - _devices = _deviceService.EnabledDevices.Where(d => d.InputIdentifiers.Any()).ToList(); + _devices = _rgbService.EnabledDevices.Where(d => d.InputIdentifiers.Any()).ToList(); } private void AddDeviceToCache(ArtemisDevice match, InputProvider provider, object identifier) @@ -241,12 +241,12 @@ internal class InputService : IInputService OnDeviceIdentified(); } - private void DeviceServiceOnDevicesModified(object? sender, DeviceEventArgs args) + private void RgbServiceOnDevicesModified(object? sender, DeviceEventArgs args) { - _firstKeyboard = _deviceService.Devices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Keyboard); - _firstMouse = _deviceService.Devices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Mouse); - _keyboardCount = _deviceService.Devices.Count(d => d.DeviceType == RGBDeviceType.Keyboard); - _mouseCount = _deviceService.Devices.Count(d => d.DeviceType == RGBDeviceType.Mouse); + _firstKeyboard = _rgbService.Devices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Keyboard); + _firstMouse = _rgbService.Devices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Mouse); + _keyboardCount = _rgbService.Devices.Count(d => d.DeviceType == RGBDeviceType.Keyboard); + _mouseCount = _rgbService.Devices.Count(d => d.DeviceType == RGBDeviceType.Mouse); BustIdentifierCache(); } diff --git a/src/Artemis.Core/Services/Interfaces/ICoreService.cs b/src/Artemis.Core/Services/Interfaces/ICoreService.cs index 8d0d1f60b..5830815ba 100644 --- a/src/Artemis.Core/Services/Interfaces/ICoreService.cs +++ b/src/Artemis.Core/Services/Interfaces/ICoreService.cs @@ -12,6 +12,21 @@ public interface ICoreService : IArtemisService /// bool IsInitialized { get; } + /// + /// The time the last frame took to render + /// + TimeSpan FrameTime { get; } + + /// + /// The amount of frames rendered each second + /// + public int FrameRate { get; } + + /// + /// Gets or sets whether profiles are rendered each frame by calling their Render method + /// + bool ProfileRenderingDisabled { get; set; } + /// /// Gets a boolean indicating whether Artemis is running in an elevated environment (admin permissions) /// @@ -26,4 +41,14 @@ public interface ICoreService : IArtemisService /// Occurs the core has finished initializing /// event EventHandler Initialized; + + /// + /// Occurs whenever a frame is rendering, after modules have rendered + /// + event EventHandler FrameRendering; + + /// + /// Occurs whenever a frame is finished rendering and the render pipeline is closed + /// + event EventHandler FrameRendered; } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Interfaces/IDeviceService.cs b/src/Artemis.Core/Services/Interfaces/IDeviceService.cs index b66f14022..c3b8c7d59 100644 --- a/src/Artemis.Core/Services/Interfaces/IDeviceService.cs +++ b/src/Artemis.Core/Services/Interfaces/IDeviceService.cs @@ -1,109 +1,13 @@ -using System; -using System.Collections.Generic; -using Artemis.Core.DeviceProviders; - -namespace Artemis.Core.Services; +namespace Artemis.Core.Services; /// /// A service that allows you manage an /// public interface IDeviceService : IArtemisService { - /// - /// Gets a read-only collection containing all enabled devices - /// - IReadOnlyCollection EnabledDevices { get; } - - /// - /// Gets a read-only collection containing all registered devices - /// - IReadOnlyCollection Devices { get; } - /// /// Identifies the device by making it blink white 5 times /// /// void IdentifyDevice(ArtemisDevice device); - - /// - /// Adds the given device provider and its devices. - /// - /// - void AddDeviceProvider(DeviceProvider deviceProvider); - - /// - /// Removes the given device provider and its devices. - /// - /// - void RemoveDeviceProvider(DeviceProvider deviceProvider); - - /// - /// Applies auto-arranging logic to the surface - /// - void AutoArrangeDevices(); - - /// - /// Apples the provided to the provided - /// - /// - /// - void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout? layout); - - /// - /// Enables the provided device - /// - /// The device to enable - void EnableDevice(ArtemisDevice device); - - /// - /// Disables the provided device - /// - /// The device to disable - void DisableDevice(ArtemisDevice device); - - /// - /// 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(); - - /// - /// Occurs when a single device was added. - /// - event EventHandler DeviceAdded; - - /// - /// Occurs when a single device was removed. - /// - event EventHandler DeviceRemoved; - - /// - /// Occurs when a single device was disabled - /// - event EventHandler DeviceEnabled; - - /// - /// Occurs when a single device was disabled. - /// - event EventHandler DeviceDisabled; - - /// - /// Occurs when a device provider was added. - /// - event EventHandler DeviceProviderAdded; - - /// - /// Occurs when a device provider was removed. - /// - event EventHandler DeviceProviderRemoved; - - /// - /// Occurs when the surface has had modifications to its LED collection - /// - event EventHandler LedsChanged; } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Interfaces/IRenderService.cs b/src/Artemis.Core/Services/Interfaces/IRenderService.cs deleted file mode 100644 index 153f4c827..000000000 --- a/src/Artemis.Core/Services/Interfaces/IRenderService.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using Artemis.Core.Services.Core; -using Artemis.Core.SkiaSharp; -using RGB.NET.Core; - -namespace Artemis.Core.Services; - -/// -/// Represents a service that manages the render loop and renderers. -/// -public interface IRenderService : IArtemisService -{ - /// - /// Gets the graphics context to be used for rendering - /// - IManagedGraphicsContext? GraphicsContext { get; } - - /// - /// Gets the RGB surface to which is being rendered. - /// - RGBSurface Surface { get; } - - /// - /// Gets a list of registered renderers. - /// - List Renderers { get; } - - /// - /// Gets or sets a boolean indicating whether rendering is paused. - /// - bool IsPaused { get; set; } - - /// - /// The time the last frame took to render - /// - TimeSpan FrameTime { get; } - - /// - /// The amount of frames rendered each second - /// - public int FrameRate { get; } - - /// - /// Initializes the render service and starts rendering. - /// - void Initialize(); - - /// - /// Occurs whenever a frame is rendering, after modules have rendered - /// - event EventHandler FrameRendering; - - /// - /// Occurs whenever a frame is finished rendering and the render pipeline is closed - /// - event EventHandler FrameRendered; -} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Interfaces/IRgbService.cs b/src/Artemis.Core/Services/Interfaces/IRgbService.cs new file mode 100644 index 000000000..df258f5bf --- /dev/null +++ b/src/Artemis.Core/Services/Interfaces/IRgbService.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using Artemis.Core.SkiaSharp; +using RGB.NET.Core; + +namespace Artemis.Core.Services; + +/// +/// A service that allows you to manage the and its contents +/// +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 + /// + RGBSurface Surface { get; set; } + + /// + /// Gets or sets whether rendering should be paused + /// + bool IsRenderPaused { get; } + + /// + /// Gets a boolean indicating whether the render pipeline is open + /// + bool RenderOpen { get; } + + /// + /// Gets or sets a boolean indicating whether to flush the RGB.NET LEDs during next update + /// + bool FlushLeds { get; set; } + + /// + /// Opens the render pipeline + /// + SKTexture OpenRender(); + + /// + /// Closes the render pipeline + /// + void CloseRender(); + + /// + /// Applies the current value of the Core.PreferredGraphicsContext setting to the graphics context. + /// + /// A boolean to indicate whether or not to force the graphics context to software mode. + void ApplyPreferredGraphicsContext(bool forceSoftware); + + /// + /// Updates the graphics context to the provided . + /// Note: The old graphics context will be used until the next frame starts rendering and is disposed afterwards. + /// + /// + /// The new managed graphics context. If , software rendering + /// is used. + /// + void UpdateGraphicsContext(IManagedGraphicsContext? managedGraphicsContext); + + /// + /// Adds the given device provider to the + /// + /// + void AddDeviceProvider(IRGBDeviceProvider deviceProvider); + + /// + /// Removes the given device provider from the + /// + /// + void RemoveDeviceProvider(IRGBDeviceProvider deviceProvider); + + /// + /// Applies auto-arranging logic to the surface + /// + 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(); + + /// + /// Enables the provided device + /// + /// The device to enable + void EnableDevice(ArtemisDevice device); + + /// + /// Disables the provided device + /// + /// The device to disable + void DisableDevice(ArtemisDevice device); + + /// + /// Pauses or resumes rendering, method won't return until the current frame finished rendering + /// + /// + /// if the pause state was changed; otherwise . + bool SetRenderPaused(bool paused); + + /// + /// Occurs when a single device was added + /// + event EventHandler DeviceAdded; + + /// + /// Occurs when a single device was removed + /// + event EventHandler DeviceRemoved; + + /// + /// Occurs when the surface has had modifications to its LED collection + /// + event EventHandler LedsChanged; +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/RenderService.cs b/src/Artemis.Core/Services/RenderService.cs deleted file mode 100644 index d9653279a..000000000 --- a/src/Artemis.Core/Services/RenderService.cs +++ /dev/null @@ -1,251 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using Artemis.Core.Providers; -using Artemis.Core.Services.Core; -using Artemis.Core.SkiaSharp; -using DryIoc; -using RGB.NET.Core; -using Serilog; -using SkiaSharp; - -namespace Artemis.Core.Services; - -internal class RenderService : IRenderService, IRenderer, IDisposable -{ - private readonly Stopwatch _frameStopWatch; - private readonly List _updateExceptions = new(); - - private readonly ILogger _logger; - private readonly IDeviceService _deviceService; - private readonly LazyEnumerable _graphicsContextProviders; - private readonly PluginSetting _targetFrameRateSetting; - private readonly PluginSetting _renderScaleSetting; - private readonly PluginSetting _preferredGraphicsContext; - - private SurfaceManager _surfaceManager; - private int _frames; - private DateTime _lastExceptionLog; - private DateTime _lastFrameRateSample; - private bool _initialized; - - public RenderService(ILogger logger, ISettingsService settingsService, IDeviceService deviceService, LazyEnumerable graphicsContextProviders) - { - _frameStopWatch = new Stopwatch(); - _logger = logger; - _deviceService = deviceService; - _graphicsContextProviders = graphicsContextProviders; - - _targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 30); - _renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.25); - _preferredGraphicsContext = settingsService.GetSetting("Core.PreferredGraphicsContext", "Software"); - _targetFrameRateSetting.SettingChanged += OnRenderSettingsChanged; - _renderScaleSetting.SettingChanged += RenderScaleSettingOnSettingChanged; - _preferredGraphicsContext.SettingChanged += PreferredGraphicsContextOnSettingChanged; - - Utilities.ShutdownRequested += UtilitiesOnShutdownRequested; - _surfaceManager = new SurfaceManager(this, GraphicsContext, _targetFrameRateSetting.Value, (float) _renderScaleSetting.Value); - } - - /// - public IManagedGraphicsContext? GraphicsContext { get; private set; } - - /// - public RGBSurface Surface => _surfaceManager.Surface; - - /// - public List Renderers { get; } = new(); - - /// - public bool IsPaused - { - get => _surfaceManager.IsPaused; - set => _surfaceManager.SetPaused(value); - } - - /// - public int FrameRate { get; private set; } - - /// - public TimeSpan FrameTime { get; private set; } - - /// - public void Render(SKCanvas canvas, double delta) - { - _frameStopWatch.Restart(); - try - { - OnFrameRendering(new FrameRenderingEventArgs(canvas, delta, _surfaceManager.Surface)); - foreach (IRenderer renderer in Renderers) - renderer.Render(canvas, delta); - } - catch (Exception e) - { - _updateExceptions.Add(e); - } - } - - /// - public void PostRender(SKTexture texture) - { - try - { - foreach (IRenderer renderer in Renderers) - renderer.PostRender(texture); - OnFrameRendered(new FrameRenderedEventArgs(texture, _surfaceManager.Surface)); - } - catch (Exception e) - { - _updateExceptions.Add(e); - } - finally - { - _frameStopWatch.Stop(); - _frames++; - - if ((DateTime.Now - _lastFrameRateSample).TotalSeconds >= 1) - { - FrameRate = _frames; - _frames = 0; - _lastFrameRateSample = DateTime.Now; - } - - FrameTime = _frameStopWatch.Elapsed; - - LogUpdateExceptions(); - } - } - - private void SetGraphicsContext() - { - if (Constants.StartupArguments.Contains("--force-software-render")) - { - _logger.Warning("Startup argument '--force-software-render' is applied, forcing software rendering"); - GraphicsContext = null; - return; - } - - if (_preferredGraphicsContext.Value == "Software") - { - GraphicsContext = null; - return; - } - - List providers = _graphicsContextProviders.ToList(); - if (!providers.Any()) - { - _logger.Warning("No graphics context provider found, defaulting to software rendering"); - GraphicsContext = null; - } - else - { - IManagedGraphicsContext? context = providers.FirstOrDefault(p => p.GraphicsContextName == _preferredGraphicsContext.Value)?.GetGraphicsContext(); - if (context == null) - _logger.Warning("No graphics context named '{Context}' found, defaulting to software rendering", _preferredGraphicsContext.Value); - - GraphicsContext = context; - } - } - - private void LogUpdateExceptions() - { - // Only log update exceptions every 10 seconds to avoid spamming the logs - if (DateTime.Now - _lastExceptionLog < TimeSpan.FromSeconds(10)) - return; - _lastExceptionLog = DateTime.Now; - - if (!_updateExceptions.Any()) - return; - - // Group by stack trace, that should gather up duplicate exceptions - foreach (IGrouping exceptions in _updateExceptions.GroupBy(e => e.StackTrace)) - _logger.Warning(exceptions.First(), "Exception was thrown {count} times during update in the last 10 seconds", exceptions.Count()); - - // When logging is finished start with a fresh slate - _updateExceptions.Clear(); - } - - private void DeviceServiceOnDeviceProviderAdded(object? sender, DeviceProviderEventArgs e) - { - _surfaceManager.AddDevices(e.Devices.Where(d => d.IsEnabled)); - } - - private void DeviceServiceOnDeviceProviderRemoved(object? sender, DeviceProviderEventArgs e) - { - _surfaceManager.RemoveDevices(e.Devices); - } - - private void DeviceServiceOnDeviceEnabled(object? sender, DeviceEventArgs e) - { - _surfaceManager.AddDevices(new List {e.Device}); - } - - private void DeviceServiceOnDeviceDisabled(object? sender, DeviceEventArgs e) - { - _surfaceManager.RemoveDevices(new List {e.Device}); - } - - private void OnRenderSettingsChanged(object? sender, EventArgs e) - { - _surfaceManager.UpdateTargetFrameRate(_targetFrameRateSetting.Value); - } - - private void RenderScaleSettingOnSettingChanged(object? sender, EventArgs e) - { - RenderScale.SetRenderScaleMultiplier((int) (1 / _renderScaleSetting.Value)); - _surfaceManager.UpdateRenderScale((float) _renderScaleSetting.Value); - } - - private void PreferredGraphicsContextOnSettingChanged(object? sender, EventArgs e) - { - SetGraphicsContext(); - _surfaceManager.UpdateGraphicsContext(GraphicsContext); - } - - private void UtilitiesOnShutdownRequested(object? sender, EventArgs e) - { - IsPaused = true; - } - - /// - public void Dispose() - { - IsPaused = true; - _surfaceManager.Dispose(); - } - - /// - public event EventHandler? FrameRendering; - - /// - public event EventHandler? FrameRendered; - - /// - public void Initialize() - { - if (_initialized) - return; - - SetGraphicsContext(); - _surfaceManager.AddDevices(_deviceService.EnabledDevices); - - _deviceService.DeviceProviderAdded += DeviceServiceOnDeviceProviderAdded; - _deviceService.DeviceProviderRemoved += DeviceServiceOnDeviceProviderRemoved; - _deviceService.DeviceEnabled += DeviceServiceOnDeviceEnabled; - _deviceService.DeviceDisabled += DeviceServiceOnDeviceDisabled; - - IsPaused = false; - _initialized = true; - } - - protected virtual void OnFrameRendering(FrameRenderingEventArgs e) - { - FrameRendering?.Invoke(this, e); - } - - protected virtual void OnFrameRendered(FrameRenderedEventArgs e) - { - FrameRendered?.Invoke(this, e); - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs new file mode 100644 index 000000000..753becc66 --- /dev/null +++ b/src/Artemis.Core/Services/RgbService.cs @@ -0,0 +1,613 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading; +using Artemis.Core.DeviceProviders; +using Artemis.Core.Providers; +using Artemis.Core.Services.Models; +using Artemis.Core.SkiaSharp; +using Artemis.Storage.Entities.Surface; +using Artemis.Storage.Repositories.Interfaces; +using DryIoc; +using RGB.NET.Core; +using Serilog; + +namespace Artemis.Core.Services; + +/// +/// Provides wrapped access the RGB.NET +/// +internal class RgbService : IRgbService +{ + private readonly ILogger _logger; + private readonly IPluginManagementService _pluginManagementService; + private readonly IDeviceRepository _deviceRepository; + private readonly LazyEnumerable _graphicsContextProviders; + + private readonly PluginSetting _preferredGraphicsContext; + private readonly PluginSetting _renderScaleSetting; + private readonly PluginSetting _targetFrameRateSetting; + + private readonly List _devices; + private readonly List _enabledDevices; + private readonly SKTextureBrush _textureBrush = new(null) {CalculationMode = RenderMode.Absolute}; + private Dictionary _ledMap; + private ListLedGroup? _surfaceLedGroup; + private SKTexture? _texture; + + public RgbService(ILogger logger, + ISettingsService settingsService, + IPluginManagementService pluginManagementService, + IDeviceRepository deviceRepository, + LazyEnumerable graphicsContextProviders) + { + _logger = logger; + _pluginManagementService = pluginManagementService; + _deviceRepository = deviceRepository; + _graphicsContextProviders = graphicsContextProviders; + + _targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 30); + _renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.25); + _preferredGraphicsContext = settingsService.GetSetting("Core.PreferredGraphicsContext", "Software"); + + Surface = new RGBSurface(); + Utilities.RenderScaleMultiplier = (int) (1 / _renderScaleSetting.Value); + + // Let's throw these for now + Surface.Exception += SurfaceOnException; + Surface.SurfaceLayoutChanged += SurfaceOnLayoutChanged; + _targetFrameRateSetting.SettingChanged += TargetFrameRateSettingOnSettingChanged; + _renderScaleSetting.SettingChanged += RenderScaleSettingOnSettingChanged; + _preferredGraphicsContext.SettingChanged += PreferredGraphicsContextOnSettingChanged; + _enabledDevices = new List(); + _devices = new List(); + _ledMap = new Dictionary(); + + EnabledDevices = new ReadOnlyCollection(_enabledDevices); + Devices = new ReadOnlyCollection(_devices); + LedMap = new ReadOnlyDictionary(_ledMap); + + UpdateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / _targetFrameRateSetting.Value}; + SetRenderPaused(true); + Surface.RegisterUpdateTrigger(UpdateTrigger); + + Utilities.ShutdownRequested += UtilitiesOnShutdownRequested; + } + + + public TimerUpdateTrigger UpdateTrigger { get; } + + protected virtual void OnDeviceRemoved(DeviceEventArgs e) + { + DeviceRemoved?.Invoke(this, e); + } + + protected virtual void OnLedsChanged() + { + LedsChanged?.Invoke(this, EventArgs.Empty); + _texture?.Invalidate(); + } + + private void UtilitiesOnShutdownRequested(object? sender, EventArgs e) + { + SetRenderPaused(true); + } + + private void SurfaceOnLayoutChanged(SurfaceLayoutChangedEventArgs args) + { + UpdateLedGroup(); + } + + private void UpdateLedGroup() + { + bool changedRenderPaused = SetRenderPaused(true); + try + { + _ledMap = new Dictionary(_devices.SelectMany(d => d.Leds).ToDictionary(l => l.RgbLed)); + LedMap = new ReadOnlyDictionary(_ledMap); + + if (_surfaceLedGroup == null) + { + _surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) {Brush = _textureBrush}; + OnLedsChanged(); + return; + } + + lock (_surfaceLedGroup) + { + // Clean up the old background + _surfaceLedGroup.Detach(); + + // Apply the application wide brush and decorator + _surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) {Brush = _textureBrush}; + OnLedsChanged(); + } + } + finally + { + if (changedRenderPaused) + SetRenderPaused(false); + } + } + + private void TargetFrameRateSettingOnSettingChanged(object? sender, EventArgs e) + { + UpdateTrigger.UpdateFrequency = 1.0 / _targetFrameRateSetting.Value; + } + + private void SurfaceOnException(ExceptionEventArgs args) + { + _logger.Warning(args.Exception, "Surface caught exception"); + throw args.Exception; + } + + private void OnDeviceAdded(DeviceEventArgs e) + { + DeviceAdded?.Invoke(this, e); + } + + private void RenderScaleSettingOnSettingChanged(object? sender, EventArgs e) + { + Utilities.RenderScaleMultiplier = (int) (1 / _renderScaleSetting.Value); + + _texture?.Invalidate(); + foreach (ArtemisDevice artemisDevice in Devices) + artemisDevice.CalculateRenderProperties(); + OnLedsChanged(); + } + + private void PreferredGraphicsContextOnSettingChanged(object? sender, EventArgs e) + { + ApplyPreferredGraphicsContext(false); + } + + public IReadOnlyCollection EnabledDevices { get; } + public IReadOnlyCollection Devices { get; } + public IReadOnlyDictionary LedMap { get; private set; } + + public RGBSurface Surface { get; set; } + public bool IsRenderPaused { get; set; } + public bool RenderOpen { get; private set; } + + /// + public bool FlushLeds { get; set; } + + public void AddDeviceProvider(IRGBDeviceProvider deviceProvider) + { + _logger.Verbose("[AddDeviceProvider] Pausing rendering to add {DeviceProvider}", deviceProvider.GetType().Name); + bool changedRenderPaused = SetRenderPaused(true); + + try + { + List toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList(); + _logger.Verbose("[AddDeviceProvider] Removing {Count} old device(s)", toRemove.Count); + Surface.Detach(toRemove.Select(d => d.RgbDevice)); + foreach (ArtemisDevice device in toRemove) + RemoveDevice(device); + + List providerExceptions = new(); + + void DeviceProviderOnException(object? sender, ExceptionEventArgs e) + { + if (e.IsCritical) + providerExceptions.Add(e.Exception); + else + _logger.Warning(e.Exception, "Device provider {deviceProvider} threw non-critical exception", deviceProvider.GetType().Name); + } + + _logger.Verbose("[AddDeviceProvider] Initializing device provider"); + deviceProvider.Exception += DeviceProviderOnException; + deviceProvider.Initialize(); + _logger.Verbose("[AddDeviceProvider] Attaching devices of device provider"); + Surface.Attach(deviceProvider.Devices); + deviceProvider.Exception -= DeviceProviderOnException; + if (providerExceptions.Count == 1) + throw new ArtemisPluginException("RGB.NET threw exception: " + providerExceptions.First().Message, providerExceptions.First()); + if (providerExceptions.Count > 1) + throw new ArtemisPluginException("RGB.NET threw multiple exceptions", new AggregateException(providerExceptions)); + + 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 + { + _logger.Verbose("[AddDeviceProvider] Updating the LED group"); + UpdateLedGroup(); + + _logger.Verbose("[AddDeviceProvider] Resuming rendering after adding {DeviceProvider}", deviceProvider.GetType().Name); + if (changedRenderPaused) + SetRenderPaused(false); + } + } + + public void RemoveDeviceProvider(IRGBDeviceProvider deviceProvider) + { + _logger.Verbose("[RemoveDeviceProvider] Pausing rendering to remove {DeviceProvider}", deviceProvider.GetType().Name); + bool changedRenderPaused = SetRenderPaused(true); + + try + { + List toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList(); + _logger.Verbose("[RemoveDeviceProvider] Removing {Count} old device(s)", toRemove.Count); + Surface.Detach(toRemove.Select(d => d.RgbDevice)); + 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 + { + _logger.Verbose("[RemoveDeviceProvider] Updating the LED group"); + UpdateLedGroup(); + + _logger.Verbose("[RemoveDeviceProvider] Resuming rendering after adding {DeviceProvider}", deviceProvider.GetType().Name); + if (changedRenderPaused) + SetRenderPaused(false); + } + } + + public void Dispose() + { + Surface.UnregisterUpdateTrigger(UpdateTrigger); + + UpdateTrigger.Dispose(); + Surface.Dispose(); + } + + public bool SetRenderPaused(bool paused) + { + if (IsRenderPaused == paused) + return false; + + if (paused) + UpdateTrigger.Stop(); + else + UpdateTrigger.Start(); + + IsRenderPaused = paused; + return true; + } + + public event EventHandler? DeviceAdded; + public event EventHandler? DeviceRemoved; + public event EventHandler? LedsChanged; + + #region Rendering + + private IManagedGraphicsContext? _newGraphicsContext; + + + public SKTexture OpenRender() + { + if (RenderOpen) + throw new ArtemisCoreException("Render pipeline is already open"); + + if (_texture == null || _texture.IsInvalid) + CreateTexture(); + + RenderOpen = true; + return _texture!; + } + + public void CloseRender() + { + if (!RenderOpen) + throw new ArtemisCoreException("Render pipeline is already closed"); + + RenderOpen = false; + _texture?.CopyPixelData(); + } + + public void CreateTexture() + { + if (RenderOpen) + throw new ArtemisCoreException("Cannot update the texture while rendering"); + + lock (_devices) + { + IManagedGraphicsContext? graphicsContext = Constants.ManagedGraphicsContext = _newGraphicsContext; + if (!ReferenceEquals(graphicsContext, _newGraphicsContext)) + graphicsContext = _newGraphicsContext; + + if (graphicsContext != null) + _logger.Debug("Creating SKTexture with graphics context {graphicsContext}", graphicsContext.GetType().Name); + else + _logger.Debug("Creating SKTexture with software-based graphics context"); + + float evenWidth = Surface.Boundary.Size.Width; + if (evenWidth % 2 != 0) + evenWidth++; + float evenHeight = Surface.Boundary.Size.Height; + if (evenHeight % 2 != 0) + evenHeight++; + + float renderScale = (float) _renderScaleSetting.Value; + int width = Math.Max(1, MathF.Min(evenWidth * renderScale, 4096).RoundToInt()); + int height = Math.Max(1, MathF.Min(evenHeight * renderScale, 4096).RoundToInt()); + + _texture?.Dispose(); + _texture = new SKTexture(graphicsContext, width, height, renderScale, Devices); + _textureBrush.Texture = _texture; + + + if (!ReferenceEquals(_newGraphicsContext, Constants.ManagedGraphicsContext = _newGraphicsContext)) + { + Constants.ManagedGraphicsContext?.Dispose(); + Constants.ManagedGraphicsContext = _newGraphicsContext; + _newGraphicsContext = null; + } + } + } + + public void ApplyPreferredGraphicsContext(bool forceSoftware) + { + if (forceSoftware) + { + _logger.Warning("Startup argument '--force-software-render' is applied, forcing software rendering"); + UpdateGraphicsContext(null); + return; + } + + if (_preferredGraphicsContext.Value == "Software") + { + UpdateGraphicsContext(null); + return; + } + + + List providers = _graphicsContextProviders.ToList(); + if (!providers.Any()) + { + _logger.Warning("No graphics context provider found, defaulting to software rendering"); + UpdateGraphicsContext(null); + return; + } + + IManagedGraphicsContext? context = providers.FirstOrDefault(p => p.GraphicsContextName == _preferredGraphicsContext.Value)?.GetGraphicsContext(); + if (context == null) + { + _logger.Warning("No graphics context named '{Context}' found, defaulting to software rendering", _preferredGraphicsContext.Value); + UpdateGraphicsContext(null); + return; + } + + UpdateGraphicsContext(context); + } + + public void UpdateGraphicsContext(IManagedGraphicsContext? managedGraphicsContext) + { + if (ReferenceEquals(managedGraphicsContext, Constants.ManagedGraphicsContext)) + return; + + _newGraphicsContext = managedGraphicsContext; + _texture?.Invalidate(); + } + + #endregion + + #region EnabledDevices + + public void AutoArrangeDevices() + { + bool changedRenderPaused = SetRenderPaused(true); + + try + { + SurfaceArrangement surfaceArrangement = SurfaceArrangement.GetDefaultArrangement(); + surfaceArrangement.Arrange(_devices); + foreach (ArtemisDevice artemisDevice in _devices) + artemisDevice.ApplyDefaultCategories(); + + SaveDevices(); + } + finally + { + if (changedRenderPaused) + SetRenderPaused(false); + } + } + + public ArtemisLayout? ApplyBestDeviceLayout(ArtemisDevice device) + { + ArtemisLayout? layout; + + // Configured layout path takes precedence over all other options + if (device.CustomLayoutPath != null) + { + layout = new ArtemisLayout(device.CustomLayoutPath, LayoutSource.Configured); + if (layout.IsValid) + { + ApplyDeviceLayout(device, layout); + return layout; + } + } + + // Look for a layout provided by the user + layout = device.DeviceProvider.LoadUserLayout(device); + if (layout.IsValid) + { + ApplyDeviceLayout(device, layout); + return layout; + } + + if (device.DisableDefaultLayout) + { + layout = null; + ApplyDeviceLayout(device, layout); + return null; + } + + // Look for a layout provided by the plugin + layout = device.DeviceProvider.LoadLayout(device); + if (layout.IsValid) + { + ApplyDeviceLayout(device, layout); + return layout; + } + + // Finally fall back to a default layout + layout = ArtemisLayout.GetDefaultLayout(device); + ApplyDeviceLayout(device, layout); + return layout; + } + + public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout? layout) + { + if (layout == null) + { + if (device.Layout != null) + ReloadDevice(device); + return; + } + + if (layout.Source == LayoutSource.Default) + device.ApplyLayout(layout, false, false); + else + device.ApplyLayout(layout, device.DeviceProvider.CreateMissingLedsSupported, device.DeviceProvider.RemoveExcessiveLedsSupported); + + UpdateLedGroup(); + } + + private void ReloadDevice(ArtemisDevice device) + { + // Any pending changes are otherwise lost including DisableDefaultLayout + device.ApplyToEntity(); + _deviceRepository.Save(device.DeviceEntity); + + DeviceProvider deviceProvider = device.DeviceProvider; + + // Feels bad but need to in order to get the initial LEDs back + _pluginManagementService.DisablePluginFeature(deviceProvider, false); + Thread.Sleep(500); + _pluginManagementService.EnablePluginFeature(deviceProvider, false); + } + + 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); + + 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); + + 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); + + 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 +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs index 4e8d357fa..59fae7a9d 100644 --- a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs +++ b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs @@ -41,11 +41,6 @@ public interface IProfileService : IArtemisService /// Gets or sets a value indicating whether the currently focused profile should receive updates. /// bool UpdateFocusProfile { get; set; } - - /// - /// Gets or sets whether profiles are rendered each frame by calling their Render method - /// - bool ProfileRenderingDisabled { get; set; } /// /// Creates a copy of the provided profile configuration. diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 485dfd267..7321fc6b2 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -5,9 +5,9 @@ using System.IO; using System.IO.Compression; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using Artemis.Core.Modules; -using Artemis.Core.Services.Core; using Artemis.Storage.Entities.Profile; using Artemis.Storage.Repositories.Interfaces; using Newtonsoft.Json; @@ -16,37 +16,36 @@ using SkiaSharp; namespace Artemis.Core.Services; -internal class ProfileService : IProfileService, IRenderer +internal class ProfileService : IProfileService { private readonly ILogger _logger; + private readonly IRgbService _rgbService; private readonly IProfileCategoryRepository _profileCategoryRepository; private readonly IPluginManagementService _pluginManagementService; - private readonly IDeviceService _deviceService; + private readonly List _pendingKeyboardEvents = new(); private readonly List _profileCategories; private readonly IProfileRepository _profileRepository; private readonly List _renderExceptions = new(); private readonly List _updateExceptions = new(); - private DateTime _lastRenderExceptionLog; private DateTime _lastUpdateExceptionLog; public ProfileService(ILogger logger, + IRgbService rgbService, IProfileCategoryRepository profileCategoryRepository, IPluginManagementService pluginManagementService, IInputService inputService, - IDeviceService deviceService, - IRenderService renderService, IProfileRepository profileRepository) { _logger = logger; + _rgbService = rgbService; _profileCategoryRepository = profileCategoryRepository; _pluginManagementService = pluginManagementService; - _deviceService = deviceService; _profileRepository = profileRepository; _profileCategories = new List(_profileCategoryRepository.GetAll().Select(c => new ProfileCategory(c)).OrderBy(c => c.Order)); - _deviceService.LedsChanged += DeviceServiceOnLedsChanged; + _rgbService.LedsChanged += RgbServiceOnLedsChanged; _pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled; _pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled; @@ -55,15 +54,11 @@ internal class ProfileService : IProfileService, IRenderer if (!_profileCategories.Any()) CreateDefaultProfileCategories(); UpdateModules(); - - renderService.Renderers.Add(this); } public ProfileConfiguration? FocusProfile { get; set; } public ProfileElement? FocusProfileElement { get; set; } public bool UpdateFocusProfile { get; set; } - - public bool ProfileRenderingDisabled { get; set; } /// public void UpdateProfiles(double deltaTime) @@ -187,21 +182,6 @@ internal class ProfileService : IProfileService, IRenderer } } } - - /// - public void Render(SKCanvas canvas, double delta) - { - if (ProfileRenderingDisabled) - return; - - UpdateProfiles(delta); - RenderProfiles(canvas); - } - - /// - public void PostRender(SKTexture texture) - { - } /// public void LoadProfileConfigurationIcon(ProfileConfiguration profileConfiguration) @@ -255,7 +235,7 @@ internal class ProfileService : IProfileService, IRenderer throw new ArtemisCoreException($"Cannot find profile named: {profileConfiguration.Name} ID: {profileConfiguration.Entity.ProfileId}"); Profile profile = new(profileConfiguration, profileEntity); - profile.PopulateLeds(_deviceService.EnabledDevices); + profile.PopulateLeds(_rgbService.EnabledDevices); if (profile.IsFreshImport) { @@ -543,7 +523,7 @@ internal class ProfileService : IProfileService, IRenderer /// public void AdaptProfile(Profile profile) { - List devices = _deviceService.EnabledDevices.ToList(); + List devices = _rgbService.EnabledDevices.ToList(); foreach (Layer layer in profile.GetAllLayers()) layer.Adapter.Adapt(devices); @@ -572,7 +552,7 @@ internal class ProfileService : IProfileService, IRenderer foreach (ProfileConfiguration profileConfiguration in ProfileConfigurations) { if (profileConfiguration.Profile == null) continue; - profileConfiguration.Profile.PopulateLeds(_deviceService.EnabledDevices); + profileConfiguration.Profile.PopulateLeds(_rgbService.EnabledDevices); if (!profileConfiguration.Profile.IsFreshImport) continue; _logger.Debug("Profile is a fresh import, adapting to surface - {profile}", profileConfiguration.Profile); @@ -593,7 +573,7 @@ internal class ProfileService : IProfileService, IRenderer } } - private void DeviceServiceOnLedsChanged(object? sender, EventArgs e) + private void RgbServiceOnLedsChanged(object? sender, EventArgs e) { ActiveProfilesPopulateLeds(); } diff --git a/src/Artemis.Core/Utilities/RenderScale.cs b/src/Artemis.Core/Utilities/RenderScale.cs deleted file mode 100644 index 43f27d54d..000000000 --- a/src/Artemis.Core/Utilities/RenderScale.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using SkiaSharp; - -namespace Artemis.Core; - -internal static class RenderScale -{ - internal static int RenderScaleMultiplier { get; private set; } = 2; - - internal static event EventHandler? RenderScaleMultiplierChanged; - - internal static void SetRenderScaleMultiplier(int renderScaleMultiplier) - { - RenderScaleMultiplier = renderScaleMultiplier; - RenderScaleMultiplierChanged?.Invoke(null, EventArgs.Empty); - } - - internal static SKRectI CreateScaleCompatibleRect(float x, float y, float width, float height) - { - int roundX = (int) MathF.Floor(x); - int roundY = (int) MathF.Floor(y); - int roundWidth = (int) MathF.Ceiling(width); - int roundHeight = (int) MathF.Ceiling(height); - - if (RenderScaleMultiplier == 1) - return SKRectI.Create(roundX, roundY, roundWidth, roundHeight); - - return SKRectI.Create( - roundX - roundX % RenderScaleMultiplier, - roundY - roundY % RenderScaleMultiplier, - roundWidth - roundWidth % RenderScaleMultiplier, - roundHeight - roundHeight % RenderScaleMultiplier - ); - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Utilities/Utilities.cs b/src/Artemis.Core/Utilities/Utilities.cs index ed0ded1d7..db6e373a0 100644 --- a/src/Artemis.Core/Utilities/Utilities.cs +++ b/src/Artemis.Core/Utilities/Utilities.cs @@ -156,4 +156,28 @@ public static class Utilities { UpdateRequested?.Invoke(null, e); } + + #region Scaling + + internal static int RenderScaleMultiplier { get; set; } = 2; + + internal static SKRectI CreateScaleCompatibleRect(float x, float y, float width, float height) + { + int roundX = (int) MathF.Floor(x); + int roundY = (int) MathF.Floor(y); + int roundWidth = (int) MathF.Ceiling(width); + int roundHeight = (int) MathF.Ceiling(height); + + if (RenderScaleMultiplier == 1) + return SKRectI.Create(roundX, roundY, roundWidth, roundHeight); + + return SKRectI.Create( + roundX - roundX % RenderScaleMultiplier, + roundY - roundY % RenderScaleMultiplier, + roundWidth - roundWidth % RenderScaleMultiplier, + roundHeight - roundHeight % RenderScaleMultiplier + ); + } + + #endregion } \ 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 84e4be2c2..6908fadd3 100644 --- a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs +++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs @@ -27,8 +27,8 @@ namespace Artemis.UI.Shared; /// public class DeviceVisualizer : Control { - internal static readonly Dictionary BitmapCache = new(); - private readonly IRenderService _renderService; + internal static readonly Dictionary BitmapCache = new(); + private readonly ICoreService _coreService; private readonly List _deviceVisualizerLeds; private Rect _deviceBounds; @@ -40,7 +40,7 @@ public class DeviceVisualizer : Control /// public DeviceVisualizer() { - _renderService = UI.Locator.Resolve(); + _coreService = UI.Locator.Resolve(); _deviceVisualizerLeds = new List(); PointerReleased += OnPointerReleased; @@ -195,6 +195,16 @@ public class DeviceVisualizer : Control SetupForDevice(); } + private void DevicePropertyChanged(object? sender, PropertyChangedEventArgs e) + { + Dispatcher.UIThread.Invoke(async () => + { + if (Device != null) + BitmapCache.Remove(Device); + await SetupForDevice(); + }, DispatcherPriority.Background); + } + private void DeviceUpdated(object? sender, EventArgs e) { Dispatcher.UIThread.Invoke(SetupForDevice, DispatcherPriority.Background); @@ -241,6 +251,7 @@ public class DeviceVisualizer : Control { if (Device != null) { + Device.RgbDevice.PropertyChanged -= DevicePropertyChanged; Device.DeviceUpdated -= DeviceUpdated; } @@ -250,7 +261,7 @@ public class DeviceVisualizer : Control /// protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { - _renderService.FrameRendered += OnFrameRendered; + _coreService.FrameRendered += OnFrameRendered; base.OnAttachedToLogicalTree(e); } @@ -258,7 +269,7 @@ public class DeviceVisualizer : Control /// protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { - _renderService.FrameRendered -= OnFrameRendered; + _coreService.FrameRendered -= OnFrameRendered; base.OnDetachedFromLogicalTree(e); } @@ -272,6 +283,7 @@ public class DeviceVisualizer : Control if (_oldDevice != null) { + _oldDevice.RgbDevice.PropertyChanged -= DevicePropertyChanged; _oldDevice.DeviceUpdated -= DeviceUpdated; } @@ -282,6 +294,7 @@ public class DeviceVisualizer : Control _deviceBounds = MeasureDevice(); _loading = true; + Device.RgbDevice.PropertyChanged += DevicePropertyChanged; Device.DeviceUpdated += DeviceUpdated; // Create all the LEDs @@ -308,15 +321,12 @@ public class DeviceVisualizer : Control private RenderTargetBitmap? GetDeviceImage(ArtemisDevice device) { - string? path = device.Layout?.Image?.LocalPath; - if (path == null) - return null; - - if (BitmapCache.TryGetValue(path, out RenderTargetBitmap? existingBitmap)) + if (BitmapCache.TryGetValue(device, out RenderTargetBitmap? existingBitmap)) return existingBitmap; - if (!File.Exists(path)) + + if (device.Layout?.Image == null || !File.Exists(device.Layout.Image.LocalPath)) { - BitmapCache[path] = null; + BitmapCache[device] = null; return null; } @@ -325,7 +335,7 @@ public class DeviceVisualizer : Control RenderTargetBitmap renderTargetBitmap = new(new PixelSize((int) device.RgbDevice.ActualSize.Width * 2, (int) device.RgbDevice.ActualSize.Height * 2)); using DrawingContext context = renderTargetBitmap.CreateDrawingContext(); - using Bitmap bitmap = new(path); + using Bitmap bitmap = new(device.Layout.Image.LocalPath); using Bitmap scaledBitmap = bitmap.CreateScaledBitmap(renderTargetBitmap.PixelSize); context.DrawImage(scaledBitmap, new Rect(scaledBitmap.Size)); @@ -335,7 +345,7 @@ public class DeviceVisualizer : Control deviceVisualizerLed.DrawBitmap(context, 2 * device.Scale); } - BitmapCache[path] = renderTargetBitmap; + BitmapCache[device] = renderTargetBitmap; return renderTargetBitmap; } diff --git a/src/Artemis.UI.Shared/Services/MainWindow/IMainWindowService.cs b/src/Artemis.UI.Shared/Services/MainWindow/IMainWindowService.cs index 0e18f6c68..d85066451 100644 --- a/src/Artemis.UI.Shared/Services/MainWindow/IMainWindowService.cs +++ b/src/Artemis.UI.Shared/Services/MainWindow/IMainWindowService.cs @@ -11,12 +11,7 @@ public interface IMainWindowService : IArtemisSharedUIService /// Gets a boolean indicating whether the main window is currently open /// bool IsMainWindowOpen { get; } - - /// - /// Gets a boolean indicating whether the main window is currently focused - /// - bool IsMainWindowFocused { get; } - + /// /// Sets up the main window provider that controls the state of the main window /// diff --git a/src/Artemis.UI.Shared/Services/MainWindow/MainWindowService.cs b/src/Artemis.UI.Shared/Services/MainWindow/MainWindowService.cs index 0a95a0087..a95126c66 100644 --- a/src/Artemis.UI.Shared/Services/MainWindow/MainWindowService.cs +++ b/src/Artemis.UI.Shared/Services/MainWindow/MainWindowService.cs @@ -10,9 +10,6 @@ internal class MainWindowService : IMainWindowService /// public bool IsMainWindowOpen { get; private set; } - /// - public bool IsMainWindowFocused { get; private set; } - protected virtual void OnMainWindowOpened() { MainWindowOpened?.Invoke(this, EventArgs.Empty); @@ -27,13 +24,11 @@ internal class MainWindowService : IMainWindowService protected virtual void OnMainWindowFocused() { MainWindowFocused?.Invoke(this, EventArgs.Empty); - IsMainWindowFocused = true; } protected virtual void OnMainWindowUnfocused() { MainWindowUnfocused?.Invoke(this, EventArgs.Empty); - IsMainWindowFocused = false; } private void SyncWithManager() diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/IToolViewModel.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/IToolViewModel.cs index 7b1bf30d8..e34febdfe 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditor/IToolViewModel.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditor/IToolViewModel.cs @@ -1,6 +1,4 @@ using System; -using Artemis.Core; -using Avalonia.Input; using Material.Icons; namespace Artemis.UI.Shared.Services.ProfileEditor; @@ -45,11 +43,6 @@ public interface IToolViewModel : IDisposable /// Gets the tooltip which this tool should show in the toolbar. /// public string ToolTip { get; } - - /// - /// Gets the keyboard hotkey that activates the tool. - /// - Hotkey? Hotkey { get; } } /// @@ -105,8 +98,5 @@ public abstract class ToolViewModel : ActivatableViewModelBase, IToolViewModel /// public abstract string ToolTip { get; } - /// - public abstract Hotkey? Hotkey { get; } - #endregion } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs index 6a69bdd3b..f0c4cf085 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs @@ -21,7 +21,6 @@ internal class ProfileEditorService : IProfileEditorService private readonly ILayerBrushService _layerBrushService; private readonly BehaviorSubject _layerPropertySubject = new(null); private readonly ILogger _logger; - private readonly IDeviceService _deviceService; private readonly IModuleService _moduleService; private readonly BehaviorSubject _pixelsPerSecondSubject = new(120); private readonly BehaviorSubject _playingSubject = new(false); @@ -29,6 +28,7 @@ internal class ProfileEditorService : IProfileEditorService private readonly Dictionary _profileEditorHistories = new(); private readonly BehaviorSubject _profileElementSubject = new(null); private readonly IProfileService _profileService; + private readonly IRgbService _rgbService; private readonly SourceList _selectedKeyframes; private readonly BehaviorSubject _suspendedEditingSubject = new(false); private readonly BehaviorSubject _timeSubject = new(TimeSpan.Zero); @@ -36,17 +36,17 @@ internal class ProfileEditorService : IProfileEditorService private ProfileEditorCommandScope? _profileEditorHistoryScope; public ProfileEditorService(ILogger logger, - IDeviceService deviceService, IProfileService profileService, IModuleService moduleService, + IRgbService rgbService, ILayerBrushService layerBrushService, IMainWindowService mainWindowService, IWindowService windowService) { _logger = logger; - _deviceService = deviceService; _profileService = profileService; _moduleService = moduleService; + _rgbService = rgbService; _layerBrushService = layerBrushService; _windowService = windowService; @@ -369,7 +369,7 @@ internal class ProfileEditorService : IProfileEditorService Layer layer = new(targetLayer.Parent, targetLayer.GetNewLayerName()); _layerBrushService.ApplyDefaultBrush(layer); - layer.AddLeds(_deviceService.EnabledDevices.SelectMany(d => d.Leds)); + layer.AddLeds(_rgbService.EnabledDevices.SelectMany(d => d.Leds)); ExecuteCommand(new AddProfileElement(layer, targetLayer.Parent, targetLayer.Order)); return layer; @@ -379,7 +379,7 @@ internal class ProfileEditorService : IProfileEditorService Layer layer = new(target, target.GetNewLayerName()); _layerBrushService.ApplyDefaultBrush(layer); - layer.AddLeds(_deviceService.EnabledDevices.SelectMany(d => d.Leds)); + layer.AddLeds(_rgbService.EnabledDevices.SelectMany(d => d.Leds)); ExecuteCommand(new AddProfileElement(layer, target, 0)); return layer; diff --git a/src/Artemis.UI.Shared/Utilities.cs b/src/Artemis.UI.Shared/Utilities.cs index 48e0ae2b3..3b1c58495 100644 --- a/src/Artemis.UI.Shared/Utilities.cs +++ b/src/Artemis.UI.Shared/Utilities.cs @@ -22,11 +22,10 @@ public static class UI static UI() { - CurrentKeyBindingsEnabled = InputElement.GotFocusEvent.Raised.Select(e => e.Item2.Source is not TextBox).StartWith(true); - CurrentKeyBindingsEnabled.Subscribe(b => KeyBindingsEnabled = b); + KeyBindingsEnabled = InputElement.GotFocusEvent.Raised.Select(e => e.Item2.Source is not TextBox).StartWith(true); MicaEnabled = MicaEnabledSubject.AsObservable(); } - + /// /// Gets the current IoC locator. /// @@ -37,15 +36,10 @@ public static class UI /// public static IClipboard Clipboard { get; set; } = null!; - /// - /// Gets an observable boolean indicating whether hotkeys are to be disabled. - /// - public static IObservable CurrentKeyBindingsEnabled { get; } - /// /// Gets a boolean indicating whether hotkeys are to be disabled. /// - public static bool KeyBindingsEnabled { get; private set; } + public static IObservable KeyBindingsEnabled { get; } /// /// Gets a boolean indicating whether the Mica effect should be enabled. diff --git a/src/Artemis.UI/Screens/Debugger/Tabs/Performance/PerformanceDebugViewModel.cs b/src/Artemis.UI/Screens/Debugger/Tabs/Performance/PerformanceDebugViewModel.cs index d61cc8c25..c21700423 100644 --- a/src/Artemis.UI/Screens/Debugger/Tabs/Performance/PerformanceDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Debugger/Tabs/Performance/PerformanceDebugViewModel.cs @@ -14,7 +14,7 @@ namespace Artemis.UI.Screens.Debugger.Performance; public class PerformanceDebugViewModel : ActivatableViewModelBase { - private readonly IRenderService _renderService; + private readonly ICoreService _coreService; private readonly IPluginManagementService _pluginManagementService; private readonly DispatcherTimer _updateTimer; private double _currentFps; @@ -22,9 +22,9 @@ public class PerformanceDebugViewModel : ActivatableViewModelBase private int _renderHeight; private int _renderWidth; - public PerformanceDebugViewModel(IRenderService renderService, IPluginManagementService pluginManagementService) + public PerformanceDebugViewModel(ICoreService coreService, IPluginManagementService pluginManagementService) { - _renderService = renderService; + _coreService = coreService; _pluginManagementService = pluginManagementService; _updateTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(500), DispatcherPriority.Background, (_, _) => Update()); @@ -87,12 +87,12 @@ public class PerformanceDebugViewModel : ActivatableViewModelBase private void HandleActivation() { Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software"; - _renderService.FrameRendered += RenderServiceOnFrameRendered; + _coreService.FrameRendered += CoreServiceOnFrameRendered; } private void HandleDeactivation() { - _renderService.FrameRendered -= RenderServiceOnFrameRendered; + _coreService.FrameRendered -= CoreServiceOnFrameRendered; } private void PopulateItems() @@ -116,9 +116,9 @@ public class PerformanceDebugViewModel : ActivatableViewModelBase viewModel.Update(); } - private void RenderServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e) + private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e) { - CurrentFps = _renderService.FrameRate; + CurrentFps = _coreService.FrameRate; SKImageInfo bitmapInfo = e.Texture.ImageInfo; RenderHeight = bitmapInfo.Height; diff --git a/src/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugViewModel.cs b/src/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugViewModel.cs index b10317022..ae18a564c 100644 --- a/src/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugViewModel.cs @@ -11,7 +11,7 @@ namespace Artemis.UI.Screens.Debugger.Render; public class RenderDebugViewModel : ActivatableViewModelBase { - private readonly IRenderService _renderService; + private readonly ICoreService _coreService; private double _currentFps; private Bitmap? _currentFrame; @@ -20,9 +20,9 @@ public class RenderDebugViewModel : ActivatableViewModelBase private int _renderHeight; private int _renderWidth; - public RenderDebugViewModel(IRenderService renderService) + public RenderDebugViewModel(ICoreService coreService) { - _renderService = renderService; + _coreService = coreService; DisplayName = "Rendering"; @@ -66,17 +66,17 @@ public class RenderDebugViewModel : ActivatableViewModelBase private void HandleActivation() { Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software"; - _renderService.FrameRendered += RenderServiceOnFrameRendered; + _coreService.FrameRendered += CoreServiceOnFrameRendered; } private void HandleDeactivation() { - _renderService.FrameRendered -= RenderServiceOnFrameRendered; + _coreService.FrameRendered -= CoreServiceOnFrameRendered; } - private void RenderServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e) + private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e) { - CurrentFps = _renderService.FrameRate; + CurrentFps = _coreService.FrameRate; using SKImage skImage = e.Texture.Surface.Snapshot(); SKImageInfo bitmapInfo = e.Texture.ImageInfo; diff --git a/src/Artemis.UI/Screens/Device/DeviceDetectInputViewModel.cs b/src/Artemis.UI/Screens/Device/DeviceDetectInputViewModel.cs index 9e70ce509..b915e14c6 100644 --- a/src/Artemis.UI/Screens/Device/DeviceDetectInputViewModel.cs +++ b/src/Artemis.UI/Screens/Device/DeviceDetectInputViewModel.cs @@ -17,16 +17,20 @@ namespace Artemis.UI.Screens.Device; public class DeviceDetectInputViewModel : ContentDialogViewModelBase { private readonly IInputService _inputService; + private readonly ListLedGroup _ledGroup; private readonly INotificationService _notificationService; + private readonly IRgbService _rgbService; - public DeviceDetectInputViewModel(ArtemisDevice device, IInputService inputService, INotificationService notificationService, IRenderService renderService) + public DeviceDetectInputViewModel(ArtemisDevice device, IInputService inputService, INotificationService notificationService, IRgbService rgbService) { _inputService = inputService; _notificationService = notificationService; + _rgbService = rgbService; + Device = device; // Create a LED group way at the top - ListLedGroup ledGroup = new(renderService.Surface, Device.Leds.Select(l => l.RgbLed)) + _ledGroup = new ListLedGroup(_rgbService.Surface, Device.Leds.Select(l => l.RgbLed)) { Brush = new SolidColorBrush(new Color(255, 255, 0)), ZIndex = 999 @@ -42,7 +46,7 @@ public class DeviceDetectInputViewModel : ContentDialogViewModelBase Disposable.Create(() => { _inputService.StopIdentify(); - ledGroup.Detach(); + _ledGroup.Detach(); }).DisposeWith(disposables); }); } diff --git a/src/Artemis.UI/Screens/Device/DevicePropertiesViewModel.cs b/src/Artemis.UI/Screens/Device/DevicePropertiesViewModel.cs index a87966726..2c9cf056a 100644 --- a/src/Artemis.UI/Screens/Device/DevicePropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/Device/DevicePropertiesViewModel.cs @@ -17,7 +17,7 @@ public class DevicePropertiesViewModel : DialogViewModelBase private readonly IDeviceVmFactory _deviceVmFactory; private ArtemisDevice _device; - public DevicePropertiesViewModel(ArtemisDevice device, IRenderService renderService, IDeviceService deviceService, IDeviceVmFactory deviceVmFactory) + public DevicePropertiesViewModel(ArtemisDevice device, ICoreService coreService, IRgbService rgbService, IDeviceVmFactory deviceVmFactory) { _deviceVmFactory = deviceVmFactory; _device = device; @@ -28,15 +28,10 @@ public class DevicePropertiesViewModel : DialogViewModelBase AddTabs(); this.WhenActivated(d => { - deviceService.DeviceAdded += DeviceServiceOnDeviceAdded; - deviceService.DeviceRemoved += DeviceServiceOnDeviceRemoved; - renderService.FrameRendering += RenderServiceOnFrameRendering; - Disposable.Create(() => - { - deviceService.DeviceAdded -= DeviceServiceOnDeviceAdded; - deviceService.DeviceRemoved -= DeviceServiceOnDeviceRemoved; - renderService.FrameRendering -= RenderServiceOnFrameRendering; - }).DisposeWith(d); + rgbService.DeviceAdded += RgbServiceOnDeviceAdded; + rgbService.DeviceRemoved += RgbServiceOnDeviceRemoved; + coreService.FrameRendering += CoreServiceOnFrameRendering; + Disposable.Create(() => coreService.FrameRendering -= CoreServiceOnFrameRendering).DisposeWith(d); }); ClearSelectedLeds = ReactiveCommand.Create(ExecuteClearSelectedLeds); @@ -52,7 +47,7 @@ public class DevicePropertiesViewModel : DialogViewModelBase public ObservableCollection Tabs { get; } public ReactiveCommand ClearSelectedLeds { get; } - private void DeviceServiceOnDeviceAdded(object? sender, DeviceEventArgs e) + private void RgbServiceOnDeviceAdded(object? sender, DeviceEventArgs e) { if (e.Device.Identifier != Device.Identifier || Device == e.Device) return; @@ -61,7 +56,7 @@ public class DevicePropertiesViewModel : DialogViewModelBase AddTabs(); } - private void DeviceServiceOnDeviceRemoved(object? sender, DeviceEventArgs e) + private void RgbServiceOnDeviceRemoved(object? sender, DeviceEventArgs e) { Tabs.Clear(); SelectedLeds.Clear(); @@ -81,7 +76,7 @@ public class DevicePropertiesViewModel : DialogViewModelBase SelectedLeds.Clear(); } - private void RenderServiceOnFrameRendering(object? sender, FrameRenderingEventArgs e) + private void CoreServiceOnFrameRendering(object? sender, FrameRenderingEventArgs e) { if (!SelectedLeds.Any()) return; diff --git a/src/Artemis.UI/Screens/Device/DeviceSettingsViewModel.cs b/src/Artemis.UI/Screens/Device/DeviceSettingsViewModel.cs index 55f01460d..52faba0d3 100644 --- a/src/Artemis.UI/Screens/Device/DeviceSettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Device/DeviceSettingsViewModel.cs @@ -18,15 +18,18 @@ public class DeviceSettingsViewModel : ActivatableViewModelBase private readonly IDeviceService _deviceService; private readonly DevicesTabViewModel _devicesTabViewModel; private readonly IDeviceVmFactory _deviceVmFactory; + private readonly IRgbService _rgbService; private readonly IWindowService _windowService; private bool _togglingDevice; - public DeviceSettingsViewModel(ArtemisDevice device, DevicesTabViewModel devicesTabViewModel, IDeviceService deviceService, IWindowService windowService, IDeviceVmFactory deviceVmFactory) + public DeviceSettingsViewModel(ArtemisDevice device, DevicesTabViewModel devicesTabViewModel, IDeviceService deviceService, IWindowService windowService, IDeviceVmFactory deviceVmFactory, + IRgbService rgbService) { _devicesTabViewModel = devicesTabViewModel; _deviceService = deviceService; _windowService = windowService; _deviceVmFactory = deviceVmFactory; + _rgbService = rgbService; Device = device; Type = Device.DeviceType.ToString().Humanize(); @@ -84,7 +87,7 @@ public class DeviceSettingsViewModel : ActivatableViewModelBase .ShowAsync(); if (viewModel.MadeChanges) - _deviceService.SaveDevice(Device); + _rgbService.SaveDevice(Device); } private async Task UpdateIsDeviceEnabled(bool value) @@ -100,9 +103,9 @@ public class DeviceSettingsViewModel : ActivatableViewModelBase value = !await _devicesTabViewModel.ShowDeviceDisableDialog(); if (value) - _deviceService.EnableDevice(Device); + _rgbService.EnableDevice(Device); else - _deviceService.DisableDevice(Device); + _rgbService.DisableDevice(Device); this.RaisePropertyChanged(nameof(IsDeviceEnabled)); SaveDevice(); @@ -115,6 +118,6 @@ public class DeviceSettingsViewModel : ActivatableViewModelBase private void SaveDevice() { - _deviceService.SaveDevice(Device); + _rgbService.SaveDevice(Device); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Device/Tabs/DeviceGeneralTabViewModel.cs b/src/Artemis.UI/Screens/Device/Tabs/DeviceGeneralTabViewModel.cs index 892b270dc..f8f21599b 100644 --- a/src/Artemis.UI/Screens/Device/Tabs/DeviceGeneralTabViewModel.cs +++ b/src/Artemis.UI/Screens/Device/Tabs/DeviceGeneralTabViewModel.cs @@ -20,8 +20,7 @@ namespace Artemis.UI.Screens.Device; public class DeviceGeneralTabViewModel : ActivatableViewModelBase { private readonly ICoreService _coreService; - private readonly IDeviceService _deviceService; - private readonly IRenderService _renderService; + private readonly IRgbService _rgbService; private readonly IWindowService _windowService; private readonly List _categories; @@ -40,11 +39,10 @@ public class DeviceGeneralTabViewModel : ActivatableViewModelBase private SKColor _currentColor; private bool _displayOnDevices; - public DeviceGeneralTabViewModel(ArtemisDevice device, ICoreService coreService, IDeviceService deviceService, IRenderService renderService, IWindowService windowService) + public DeviceGeneralTabViewModel(ArtemisDevice device, ICoreService coreService, IRgbService rgbService, IWindowService windowService) { _coreService = coreService; - _deviceService = deviceService; - _renderService = renderService; + _rgbService = rgbService; _windowService = windowService; _categories = new List(device.Categories); @@ -68,11 +66,11 @@ public class DeviceGeneralTabViewModel : ActivatableViewModelBase this.WhenActivated(d => { - _renderService.FrameRendering += OnFrameRendering; + _coreService.FrameRendering += OnFrameRendering; Disposable.Create(() => { - _renderService.FrameRendering -= OnFrameRendering; + _coreService.FrameRendering -= OnFrameRendering; Apply(); }).DisposeWith(d); }); @@ -193,12 +191,17 @@ public class DeviceGeneralTabViewModel : ActivatableViewModelBase return; await Task.Delay(400); - _deviceService.SaveDevice(Device); - _deviceService.ApplyDeviceLayout(Device, Device.GetBestDeviceLayout()); + _rgbService.SaveDevice(Device); + _rgbService.ApplyBestDeviceLayout(Device); } private void Apply() { + // TODO: Validation + + _coreService.ProfileRenderingDisabled = true; + Thread.Sleep(100); + Device.X = X; Device.Y = Y; Device.Scale = Scale; @@ -210,7 +213,9 @@ public class DeviceGeneralTabViewModel : ActivatableViewModelBase foreach (DeviceCategory deviceCategory in _categories) Device.Categories.Add(deviceCategory); - _deviceService.SaveDevice(Device); + _rgbService.SaveDevice(Device); + + _coreService.ProfileRenderingDisabled = false; } public void ApplyScaling() @@ -218,7 +223,8 @@ public class DeviceGeneralTabViewModel : ActivatableViewModelBase Device.RedScale = RedScale / 100f; Device.GreenScale = GreenScale / 100f; Device.BlueScale = BlueScale / 100f; - Device.RgbDevice.Update(true); + + _rgbService.FlushLeds = true; } public void ResetScaling() @@ -226,7 +232,6 @@ public class DeviceGeneralTabViewModel : ActivatableViewModelBase RedScale = _initialRedScale * 100; GreenScale = _initialGreenScale * 100; BlueScale = _initialBlueScale * 100; - Device.RgbDevice.Update(true); } private void OnFrameRendering(object? sender, FrameRenderingEventArgs e) diff --git a/src/Artemis.UI/Screens/Device/Tabs/DeviceLayoutTabViewModel.cs b/src/Artemis.UI/Screens/Device/Tabs/DeviceLayoutTabViewModel.cs index 419243e9a..a0a11ca17 100644 --- a/src/Artemis.UI/Screens/Device/Tabs/DeviceLayoutTabViewModel.cs +++ b/src/Artemis.UI/Screens/Device/Tabs/DeviceLayoutTabViewModel.cs @@ -22,13 +22,20 @@ public class DeviceLayoutTabViewModel : ActivatableViewModelBase { private readonly IWindowService _windowService; private readonly INotificationService _notificationService; - private readonly IDeviceService _deviceService; + private readonly ICoreService _coreService; + private readonly IRgbService _rgbService; - public DeviceLayoutTabViewModel(IWindowService windowService, INotificationService notificationService, IDeviceService deviceService, ArtemisDevice device) + public DeviceLayoutTabViewModel( + IWindowService windowService, + INotificationService notificationService, + ICoreService coreService, + IRgbService rgbService, + ArtemisDevice device) { _windowService = windowService; _notificationService = notificationService; - _deviceService = deviceService; + _coreService = coreService; + _rgbService = rgbService; Device = device; DisplayName = "Layout"; @@ -37,7 +44,11 @@ public class DeviceLayoutTabViewModel : ActivatableViewModelBase this.WhenActivated(d => { Device.PropertyChanged += DeviceOnPropertyChanged; - Disposable.Create(() => Device.PropertyChanged -= DeviceOnPropertyChanged).DisposeWith(d); + + Disposable.Create(() => + { + Device.PropertyChanged -= DeviceOnPropertyChanged; + }).DisposeWith(d); }); } @@ -47,7 +58,7 @@ public class DeviceLayoutTabViewModel : ActivatableViewModelBase public string? ImagePath => Device.Layout?.Image?.LocalPath; - public string? CustomLayoutPath => Device.CustomLayoutPath; + public string CustomLayoutPath => Device.CustomLayoutPath; public bool HasCustomLayout => Device.CustomLayoutPath != null; @@ -57,8 +68,6 @@ public class DeviceLayoutTabViewModel : ActivatableViewModelBase _notificationService.CreateNotification() .WithMessage("Cleared imported layout.") .WithSeverity(NotificationSeverity.Informational); - - _deviceService.SaveDevice(Device); } public async Task BrowseCustomLayout() @@ -75,8 +84,6 @@ public class DeviceLayoutTabViewModel : ActivatableViewModelBase .WithTitle("Imported layout") .WithMessage($"File loaded from {files[0]}") .WithSeverity(NotificationSeverity.Informational); - - _deviceService.SaveDevice(Device); } } @@ -153,10 +160,6 @@ public class DeviceLayoutTabViewModel : ActivatableViewModelBase private void DeviceOnPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName is nameof(Device.CustomLayoutPath) or nameof(Device.DisableDefaultLayout)) - { - Task.Run(() => _deviceService.ApplyDeviceLayout(Device, Device.GetBestDeviceLayout())); - this.RaisePropertyChanged(nameof(CustomLayoutPath)); - this.RaisePropertyChanged(nameof(HasCustomLayout)); - } + Task.Run(() => _rgbService.ApplyBestDeviceLayout(Device)); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Device/Tabs/InputMappingsTabViewModel.cs b/src/Artemis.UI/Screens/Device/Tabs/InputMappingsTabViewModel.cs index a96395c7e..4d3acd760 100644 --- a/src/Artemis.UI/Screens/Device/Tabs/InputMappingsTabViewModel.cs +++ b/src/Artemis.UI/Screens/Device/Tabs/InputMappingsTabViewModel.cs @@ -16,17 +16,17 @@ namespace Artemis.UI.Screens.Device; public class InputMappingsTabViewModel : ActivatableViewModelBase { private readonly IInputService _inputService; - private readonly IDeviceService _deviceService; + private readonly IRgbService _rgbService; private readonly ObservableCollection _selectedLeds; private ArtemisLed? _selectedLed; - public InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection selectedLeds, IInputService inputService, IDeviceService deviceService) + public InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection selectedLeds, IRgbService rgbService, IInputService inputService) { if (device.DeviceType != RGBDeviceType.Keyboard) throw new ArtemisUIException("The input mappings tab only supports keyboards"); + _rgbService = rgbService; _inputService = inputService; - _deviceService = deviceService; _selectedLeds = selectedLeds; Device = device; @@ -81,7 +81,7 @@ public class InputMappingsTabViewModel : ActivatableViewModelBase // Apply the new LED mapping Device.InputMappings[SelectedLed] = artemisLed; - _deviceService.SaveDevice(Device); + _rgbService.SaveDevice(Device); _selectedLeds.Clear(); UpdateInputMappings(); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs index 7b323ab6e..9209789a1 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs @@ -58,7 +58,7 @@ public class MenuBarViewModel : ActivatableViewModelBase _focusNone = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.None).ToProperty(this, vm => vm.FocusNone).DisposeWith(d); _focusFolder = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.Folder).ToProperty(this, vm => vm.FocusFolder).DisposeWith(d); _focusSelection = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.Selection).ToProperty(this, vm => vm.FocusSelection).DisposeWith(d); - _keyBindingsEnabled = Shared.UI.CurrentKeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d); + _keyBindingsEnabled = Shared.UI.KeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d); }); AddFolder = ReactiveCommand.Create(ExecuteAddFolder); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs index d7fc9516e..9f6009934 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs @@ -53,7 +53,7 @@ public class PlaybackViewModel : ActivatableViewModelBase _currentTime = _profileEditorService.Time.ToProperty(this, vm => vm.CurrentTime).DisposeWith(d); _formattedCurrentTime = _profileEditorService.Time.Select(t => $"{Math.Floor(t.TotalSeconds):00}.{t.Milliseconds:000}").ToProperty(this, vm => vm.FormattedCurrentTime).DisposeWith(d); _playing = _profileEditorService.Playing.ToProperty(this, vm => vm.Playing).DisposeWith(d); - _keyBindingsEnabled = Shared.UI.CurrentKeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d); + _keyBindingsEnabled = Shared.UI.KeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d); // Update timer Timer updateTimer = new(TimeSpan.FromMilliseconds(60.0 / 1000)); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/Dialogs/LayerHintsDialogViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/Dialogs/LayerHintsDialogViewModel.cs index edd5b07ec..5072135a1 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/Dialogs/LayerHintsDialogViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/Dialogs/LayerHintsDialogViewModel.cs @@ -15,12 +15,12 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs; public class LayerHintsDialogViewModel : DialogViewModelBase { - private readonly IDeviceService _deviceService; + private readonly IRgbService _rgbService; private readonly ILayerHintVmFactory _vmFactory; - public LayerHintsDialogViewModel(Layer layer, IDeviceService deviceService, ILayerHintVmFactory vmFactory) + public LayerHintsDialogViewModel(Layer layer, IRgbService rgbService, ILayerHintVmFactory vmFactory) { - _deviceService = deviceService; + _rgbService = rgbService; _vmFactory = vmFactory; Layer = layer; @@ -49,7 +49,7 @@ public class LayerHintsDialogViewModel : DialogViewModelBase public void AutoDetermineHints() { - Layer.Adapter.DetermineHints(_deviceService.EnabledDevices); + Layer.Adapter.DetermineHints(_rgbService.EnabledDevices); } public void AddCategoryHint() diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemViewModel.cs index df30c8b48..3e767e93f 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemViewModel.cs @@ -14,17 +14,17 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree; public class FolderTreeItemViewModel : TreeItemViewModel { - private readonly IDeviceService _deviceService; + private readonly IRgbService _rgbService; public FolderTreeItemViewModel(TreeItemViewModel? parent, Folder folder, IWindowService windowService, - IDeviceService deviceService, IProfileEditorService profileEditorService, + IRgbService rgbService, IProfileEditorVmFactory profileEditorVmFactory) - : base(parent, folder, windowService, deviceService, profileEditorService, profileEditorVmFactory) + : base(parent, folder, windowService, rgbService, profileEditorService, profileEditorVmFactory) { - _deviceService = deviceService; + _rgbService = rgbService; Folder = folder; } @@ -63,7 +63,7 @@ public class FolderTreeItemViewModel : TreeItemViewModel pasted.Name = parent.GetNewFolderName(pasted.Name + " - copy"); ProfileEditorService.ExecuteCommand(new AddProfileElement(pasted, parent, Folder.Order - 1)); - Folder.Profile.PopulateLeds(_deviceService.EnabledDevices); + Folder.Profile.PopulateLeds(_rgbService.EnabledDevices); } private async Task ExecutePasteInto() diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/LayerTreeItemViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/LayerTreeItemViewModel.cs index 83554ac12..920674f17 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/LayerTreeItemViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/LayerTreeItemViewModel.cs @@ -14,17 +14,17 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree; public class LayerTreeItemViewModel : TreeItemViewModel { - private readonly IDeviceService _deviceService; + private readonly IRgbService _rgbService; public LayerTreeItemViewModel(TreeItemViewModel? parent, Layer layer, IWindowService windowService, IProfileEditorService profileEditorService, - IDeviceService deviceService, + IRgbService rgbService, IProfileEditorVmFactory profileEditorVmFactory) - : base(parent, layer, windowService, deviceService, profileEditorService, profileEditorVmFactory) + : base(parent, layer, windowService, rgbService, profileEditorService, profileEditorVmFactory) { - _deviceService = deviceService; + _rgbService = rgbService; Layer = layer; } @@ -43,7 +43,7 @@ public class LayerTreeItemViewModel : TreeItemViewModel Layer copied = new(Layer.Profile, Layer.Parent, copy, true); ProfileEditorService.ExecuteCommand(new AddProfileElement(copied, Layer.Parent, Layer.Order - 1)); - Layer.Profile.PopulateLeds(_deviceService.EnabledDevices); + Layer.Profile.PopulateLeds(_rgbService.EnabledDevices); } protected override async Task ExecuteCopy() @@ -65,7 +65,7 @@ public class LayerTreeItemViewModel : TreeItemViewModel pasted.Name = parent.GetNewLayerName(pasted.Name + " - copy"); ProfileEditorService.ExecuteCommand(new AddProfileElement(pasted, parent, Layer.Order - 1)); - Layer.Profile.PopulateLeds(_deviceService.EnabledDevices); + Layer.Profile.PopulateLeds(_rgbService.EnabledDevices); } /// diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs index 82c559e98..6d46177b7 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs @@ -23,8 +23,8 @@ public class ProfileTreeViewModel : TreeItemViewModel private ObservableAsPropertyHelper? _keyBindingsEnabled; private TreeItemViewModel? _selectedChild; - public ProfileTreeViewModel(IWindowService windowService, IDeviceService deviceService, IProfileEditorService profileEditorService, IProfileEditorVmFactory profileEditorVmFactory) - : base(null, null, windowService, deviceService, profileEditorService, profileEditorVmFactory) + public ProfileTreeViewModel(IWindowService windowService, IRgbService rgbService, IProfileEditorService profileEditorService, IProfileEditorVmFactory profileEditorVmFactory) + : base(null, null, windowService, rgbService, profileEditorService, profileEditorVmFactory) { this.WhenActivated(d => { @@ -46,7 +46,7 @@ public class ProfileTreeViewModel : TreeItemViewModel _focusNone = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.None).ToProperty(this, vm => vm.FocusNone).DisposeWith(d); _focusFolder = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.Folder).ToProperty(this, vm => vm.FocusFolder).DisposeWith(d); _focusSelection = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.Selection).ToProperty(this, vm => vm.FocusSelection).DisposeWith(d); - _keyBindingsEnabled = Shared.UI.CurrentKeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d); + _keyBindingsEnabled = Shared.UI.KeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d); }); ClearSelection = ReactiveCommand.Create(() => profileEditorService.ChangeCurrentProfileElement(null), this.WhenAnyValue(vm => vm.KeyBindingsEnabled)); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs index 16594700a..9a35c54eb 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs @@ -28,7 +28,7 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase { private readonly IProfileEditorVmFactory _profileEditorVmFactory; private readonly IWindowService _windowService; - private readonly IDeviceService _deviceService; + private readonly IRgbService _rgbService; protected readonly IProfileEditorService ProfileEditorService; private bool _canPaste; private RenderProfileElement? _currentProfileElement; @@ -41,13 +41,13 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase protected TreeItemViewModel(TreeItemViewModel? parent, ProfileElement? profileElement, IWindowService windowService, - IDeviceService deviceService, + IRgbService rgbService, IProfileEditorService profileEditorService, IProfileEditorVmFactory profileEditorVmFactory) { ProfileEditorService = profileEditorService; _windowService = windowService; - _deviceService = deviceService; + _rgbService = rgbService; _profileEditorVmFactory = profileEditorVmFactory; Parent = parent; @@ -267,7 +267,7 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase if (ProfileElement is not Layer layer) return; - ProfileEditorService.ExecuteCommand(new ApplyAdaptionHints(layer, _deviceService.EnabledDevices.ToList())); + ProfileEditorService.ExecuteCommand(new ApplyAdaptionHints(layer, _rgbService.EnabledDevices.ToList())); } private async void UpdateCanPaste(bool isFlyoutOpen) diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineEasingView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineEasingView.axaml index ef1a69c32..e925b4edb 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineEasingView.axaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineEasingView.axaml @@ -3,21 +3,17 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:keyframes="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Keyframes" - xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Keyframes.TimelineEasingView" x:DataType="keyframes:TimelineEasingViewModel"> - - - + - - - + Margin="0 0 10 0" /> + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineEasingViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineEasingViewModel.cs index 3c196c0a1..cada48d40 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineEasingViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineEasingViewModel.cs @@ -1,10 +1,8 @@ using System.Collections.Generic; -using System.Reactive; using Artemis.Core; using Artemis.UI.Shared; using Avalonia; using Humanizer; -using ReactiveUI; namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Keyframes; @@ -12,12 +10,11 @@ public class TimelineEasingViewModel : ViewModelBase { private readonly ILayerPropertyKeyframe _keyframe; - public TimelineEasingViewModel(Easings.Functions easingFunction, ILayerPropertyKeyframe keyframe, ReactiveCommand selectEasingFunction) + public TimelineEasingViewModel(Easings.Functions easingFunction, ILayerPropertyKeyframe keyframe) { _keyframe = keyframe; EasingFunction = easingFunction; - SelectEasingFunction = selectEasingFunction; Description = easingFunction.Humanize(); EasingPoints = new List(); @@ -30,7 +27,6 @@ public class TimelineEasingViewModel : ViewModelBase } public Easings.Functions EasingFunction { get; } - public ReactiveCommand SelectEasingFunction { get; } public List EasingPoints { get; } public string Description { get; } public bool IsEasingModeSelected => _keyframe.EasingFunction == EasingFunction; diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeView.axaml index 7977a73d9..f72a45823 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeView.axaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeView.axaml @@ -3,7 +3,6 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" - xmlns:keyframes="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Keyframes" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Keyframes.TimelineKeyframeView" ClipToBounds="False"> @@ -35,9 +34,16 @@ - diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeViewModel.cs index 2f99088af..da70e2b5f 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeViewModel.cs @@ -54,12 +54,10 @@ public class TimelineKeyframeViewModel : ActivatableViewModelBase, ITimelineK Copy = ReactiveCommand.CreateFromTask(ExecuteCopy); Paste = ReactiveCommand.CreateFromTask(ExecutePaste); Delete = ReactiveCommand.Create(ExecuteDelete); - SelectEasingFunction = ReactiveCommand.Create(ExecuteSelectEasingFunction); } public LayerPropertyKeyframe LayerPropertyKeyframe { get; } public ObservableCollection EasingViewModels { get; } - public double X { @@ -95,8 +93,7 @@ public class TimelineKeyframeViewModel : ActivatableViewModelBase, ITimelineK public ReactiveCommand Copy { get; } public ReactiveCommand Paste { get; } public ReactiveCommand Delete { get; } - public ReactiveCommand SelectEasingFunction { get; } - + public bool IsSelected => _isSelected?.Value ?? false; public TimeSpan Position => LayerPropertyKeyframe.Position; public ILayerPropertyKeyframe Keyframe => LayerPropertyKeyframe; @@ -258,10 +255,10 @@ public class TimelineKeyframeViewModel : ActivatableViewModelBase, ITimelineK EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions)) .Cast() - .Select(e => new TimelineEasingViewModel(e, Keyframe, SelectEasingFunction))); + .Select(e => new TimelineEasingViewModel(e, Keyframe))); } - private void ExecuteSelectEasingFunction(Easings.Functions easingFunction) + public void SelectEasingFunction(Easings.Functions easingFunction) { _profileEditorService.ExecuteCommand(new ChangeKeyframeEasing(Keyframe, easingFunction)); } diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionAddToolViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionAddToolViewModel.cs index 484bca7f4..b1bdf7297 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionAddToolViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionAddToolViewModel.cs @@ -7,7 +7,6 @@ using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor.Commands; -using Avalonia.Input; using Material.Icons; using ReactiveUI; using SkiaSharp; @@ -18,14 +17,14 @@ public class SelectionAddToolViewModel : ToolViewModel { private readonly ObservableAsPropertyHelper? _isEnabled; private readonly IProfileEditorService _profileEditorService; - private readonly IDeviceService _deviceService; + private readonly IRgbService _rgbService; private Layer? _layer; /// - public SelectionAddToolViewModel(IProfileEditorService profileEditorService, IDeviceService deviceService) + public SelectionAddToolViewModel(IProfileEditorService profileEditorService, IRgbService rgbService) { _profileEditorService = profileEditorService; - _deviceService = deviceService; + _rgbService = rgbService; // Not disposed when deactivated but when really disposed _isEnabled = profileEditorService.ProfileElement.Select(p => p is Layer).ToProperty(this, vm => vm.IsEnabled); @@ -46,12 +45,9 @@ public class SelectionAddToolViewModel : ToolViewModel /// public override MaterialIconKind Icon => MaterialIconKind.SelectionDrag; - - /// - public override Hotkey? Hotkey { get; } = new(KeyboardKey.OemPlus, KeyboardModifierKey.Control); /// - public override string ToolTip => "Add LEDs to the current layer (Ctrl + +)"; + public override string ToolTip => "Add LEDs to the current layer"; public void AddLedsInRectangle(SKRect rect, bool expand, bool inverse) { @@ -61,7 +57,7 @@ public class SelectionAddToolViewModel : ToolViewModel if (inverse) { List toRemove = _layer.Leds.Where(l => l.AbsoluteRectangle.IntersectsWith(rect)).ToList(); - List toAdd = _deviceService.EnabledDevices.SelectMany(d => d.Leds).Where(l => l.AbsoluteRectangle.IntersectsWith(rect)).Except(toRemove).ToList(); + List toAdd = _rgbService.EnabledDevices.SelectMany(d => d.Leds).Where(l => l.AbsoluteRectangle.IntersectsWith(rect)).Except(toRemove).ToList(); List leds = _layer.Leds.Except(toRemove).ToList(); leds.AddRange(toAdd); @@ -69,7 +65,7 @@ public class SelectionAddToolViewModel : ToolViewModel } else { - List leds = _deviceService.EnabledDevices.SelectMany(d => d.Leds).Where(l => l.AbsoluteRectangle.IntersectsWith(rect)).ToList(); + List leds = _rgbService.EnabledDevices.SelectMany(d => d.Leds).Where(l => l.AbsoluteRectangle.IntersectsWith(rect)).ToList(); if (expand) leds.AddRange(_layer.Leds); _profileEditorService.ExecuteCommand(new ChangeLayerLeds(_layer, leds.Distinct().ToList())); diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolViewModel.cs index 806b9279a..2c7d0a995 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolViewModel.cs @@ -4,10 +4,8 @@ using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; using Artemis.Core; -using Artemis.Core.Services; using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor.Commands; -using Avalonia.Input; using Material.Icons; using ReactiveUI; using SkiaSharp; @@ -40,15 +38,12 @@ public class SelectionRemoveToolViewModel : ToolViewModel /// public override int Order => 3; - - /// - public override Hotkey? Hotkey { get; } = new(KeyboardKey.OemMinus, KeyboardModifierKey.Control); /// public override MaterialIconKind Icon => MaterialIconKind.SelectOff; /// - public override string ToolTip => "Remove LEDs from the current layer (Ctrl + -)"; + public override string ToolTip => "Remove LEDs from the current layer"; public void RemoveLedsInRectangle(SKRect rect) { diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolViewModel.cs index dbb5c75bf..7475f15eb 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolViewModel.cs @@ -3,13 +3,11 @@ using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; using Artemis.Core; -using Artemis.Core.Services; using Artemis.UI.Exceptions; using Artemis.UI.Shared.Extensions; using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor.Commands; using Avalonia; -using Avalonia.Input; using Material.Icons; using ReactiveUI; using SkiaSharp; @@ -97,15 +95,12 @@ public class TransformToolViewModel : ToolViewModel /// public override int Order => 3; - - /// - public override Hotkey? Hotkey { get; } = new(KeyboardKey.T, KeyboardModifierKey.Control); /// public override MaterialIconKind Icon => MaterialIconKind.TransitConnectionVariant; /// - public override string ToolTip => "Transform the shape of the current layer (Ctrl+T)"; + public override string ToolTip => "Transform the shape of the current layer"; public Rect ShapeBounds { diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorViewModel.cs index 140548d88..256595ce8 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorViewModel.cs @@ -25,7 +25,7 @@ public class VisualEditorViewModel : ActivatableViewModelBase private ObservableAsPropertyHelper? _suspendedEditing; private ReadOnlyObservableCollection? _tools; - public VisualEditorViewModel(IProfileEditorService profileEditorService, IDeviceService deviceService, IProfileEditorVmFactory vmFactory) + public VisualEditorViewModel(IProfileEditorService profileEditorService, IRgbService rgbService, IProfileEditorVmFactory vmFactory) { _vmFactory = vmFactory; _visualizers = new SourceList(); @@ -34,7 +34,7 @@ public class VisualEditorViewModel : ActivatableViewModelBase .Bind(out ReadOnlyObservableCollection visualizers) .Subscribe(); - Devices = new ObservableCollection(deviceService.EnabledDevices.OrderBy(d => d.ZIndex)); + Devices = new ObservableCollection(rgbService.EnabledDevices.OrderBy(d => d.ZIndex)); Visualizers = visualizers; this.WhenActivated(d => diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml index 0ea17649e..1939fbbae 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorView.axaml @@ -8,11 +8,19 @@ xmlns:shared="clr-namespace:Artemis.UI.Shared.Services.ProfileEditor;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.ProfileEditor.ProfileEditorView" - x:DataType="profileEditor:ProfileEditorViewModel" - Focusable="True"> + x:DataType="profileEditor:ProfileEditorViewModel"> + + + + + + + + +