1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 13:28:33 +00:00

Revert "Revert "Merge branch 'development'""

This reverts commit ebf40992bcdd75d6024d4bce25410511cbf88287.
This commit is contained in:
Robert 2023-10-17 19:54:19 +02:00
parent ebf40992bc
commit 4737173c2a
62 changed files with 1489 additions and 1254 deletions

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using Artemis.Core.DeviceProviders;
namespace Artemis.Core;
/// <summary>
/// Provides data about device provider related events
/// </summary>
public class DeviceProviderEventArgs : EventArgs
{
internal DeviceProviderEventArgs(DeviceProvider deviceProvider, List<ArtemisDevice> devices)
{
DeviceProvider = deviceProvider;
Devices = devices;
}
/// <summary>
/// Gets the device provider the event is related to.
/// </summary>
public DeviceProvider DeviceProvider { get; }
/// <summary>
/// Gets a list of the affected devices.
/// </summary>
public List<ArtemisDevice> Devices { get; set; }
}

View File

@ -1,4 +1,5 @@
using Artemis.Core.Services;
using System;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile;
namespace Artemis.Core;
@ -16,6 +17,14 @@ public class Hotkey : CorePropertyChanged, IStorageModel
Entity = new ProfileConfigurationHotkeyEntity();
}
/// <inheritdoc />
public Hotkey(KeyboardKey? key, KeyboardModifierKey? modifiers)
{
Key = key;
Modifiers = modifiers;
Entity = new ProfileConfigurationHotkeyEntity();
}
/// <summary>
/// Creates a new instance of <see cref="Hotkey" /> based on the provided entity
/// </summary>
@ -46,7 +55,7 @@ public class Hotkey : CorePropertyChanged, IStorageModel
/// <returns><see langword="true" /> if the event args match the hotkey; otherwise <see langword="false" /></returns>
public bool MatchesEventArgs(ArtemisKeyboardKeyEventArgs eventArgs)
{
return eventArgs.Key == Key && eventArgs.Modifiers == Modifiers;
return eventArgs.Key == Key && (eventArgs.Modifiers == Modifiers || (eventArgs.Modifiers == KeyboardModifierKey.None && Modifiers == null));
}
#region Implementation of IStorageModel

View File

@ -15,11 +15,17 @@ namespace Artemis.Core;
/// </summary>
public class ArtemisDevice : CorePropertyChanged
{
private readonly List<OriginalLed> _originalLeds;
private readonly Size _originalSize;
private SKPath? _path;
private SKRect _rectangle;
internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider)
{
_originalLeds = new List<OriginalLed>(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;
@ -48,6 +54,10 @@ public class ArtemisDevice : CorePropertyChanged
internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider, DeviceEntity deviceEntity)
{
_originalLeds = new List<OriginalLed>(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;
@ -350,6 +360,40 @@ public class ArtemisDevice : CorePropertyChanged
return artemisLed;
}
/// <summary>
/// Returns the most preferred device layout for this device.
/// </summary>
/// <returns>The most preferred device layout for this device.</returns>
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;
}
/// <summary>
/// Occurs when the underlying RGB.NET device was updated
/// </summary>
@ -415,8 +459,18 @@ public class ArtemisDevice : CorePropertyChanged
/// A boolean indicating whether to remove excess LEDs present in the device but missing
/// in the layout
/// </param>
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");
@ -424,18 +478,33 @@ 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

View File

@ -59,13 +59,13 @@ public class ArtemisLed : CorePropertyChanged
internal void CalculateRectangles()
{
Rectangle = Utilities.CreateScaleCompatibleRect(
Rectangle = RenderScale.CreateScaleCompatibleRect(
RgbLed.Boundary.Location.X,
RgbLed.Boundary.Location.Y,
RgbLed.Boundary.Size.Width,
RgbLed.Boundary.Size.Height
);
AbsoluteRectangle = Utilities.CreateScaleCompatibleRect(
AbsoluteRectangle = RenderScale.CreateScaleCompatibleRect(
RgbLed.AbsoluteBoundary.Location.X,
RgbLed.AbsoluteBoundary.Location.Y,
RgbLed.AbsoluteBoundary.Size.Width,

View File

@ -0,0 +1,19 @@
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; }
}

View File

@ -0,0 +1,23 @@
using SkiaSharp;
namespace Artemis.Core.Services.Core;
/// <summary>
/// Represents a renderer that renders to a canvas.
/// </summary>
public interface IRenderer
{
/// <summary>
/// Renders to the provided canvas, the delta is the time in seconds since the last time <see cref="Render" /> was
/// called.
/// </summary>
/// <param name="canvas">The canvas to render to.</param>
/// <param name="delta">The time in seconds since the last time <see cref="Render" /> was called.</param>
void Render(SKCanvas canvas, double delta);
/// <summary>
/// Called after the rendering has taken place.
/// </summary>
/// <param name="texture">The texture that the render resulted in.</param>
void PostRender(SKTexture texture);
}

View File

@ -0,0 +1,222 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Core.SkiaSharp;
using RGB.NET.Core;
using SkiaSharp;
namespace Artemis.Core.Services.Core;
/// <summary>
/// An engine drivers an update loop for a set of devices using a graphics context
/// </summary>
internal sealed class SurfaceManager : IDisposable
{
private readonly IRenderer _renderer;
private readonly TimerUpdateTrigger _updateTrigger;
private readonly object _renderLock = new();
private readonly List<ArtemisDevice> _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<ArtemisDevice> 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<ArtemisDevice> 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<Led> 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;
/// <inheritdoc />
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;
}
}
}

View File

@ -20,19 +20,11 @@ namespace Artemis.Core.Services;
/// </summary>
internal class CoreService : ICoreService
{
private readonly Stopwatch _frameStopWatch;
private readonly ILogger _logger;
private readonly PluginSetting<LogEventLevel> _loggingLevel;
private readonly IModuleService _moduleService;
private readonly IPluginManagementService _pluginManagementService;
private readonly IProfileService _profileService;
private readonly IRgbService _rgbService;
private readonly IScriptingService _scriptingService;
private readonly List<Exception> _updateExceptions = new();
private readonly IRenderService _renderService;
private int _frames;
private DateTime _lastExceptionLog;
private DateTime _lastFrameRateSample;
// ReSharper disable UnusedParameter.Local
public CoreService(IContainer container,
@ -40,36 +32,49 @@ internal class CoreService : ICoreService
StorageMigrationService _1, // injected to ensure migration runs early
ISettingsService settingsService,
IPluginManagementService pluginManagementService,
IRgbService rgbService,
IProfileService profileService,
IModuleService moduleService,
IScriptingService scriptingService)
IScriptingService scriptingService,
IRenderService renderService)
{
Constants.CorePlugin.Container = container;
_logger = logger;
_pluginManagementService = pluginManagementService;
_rgbService = rgbService;
_profileService = profileService;
_moduleService = moduleService;
_scriptingService = scriptingService;
_renderService = renderService;
_loggingLevel = settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Debug);
_frameStopWatch = new Stopwatch();
_rgbService.Surface.Updating += SurfaceOnUpdating;
_loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel();
}
public bool IsElevated { get; set; }
// ReSharper restore UnusedParameter.Local
public bool IsInitialized { get; set; }
protected virtual void OnFrameRendering(FrameRenderingEventArgs e)
public void Initialize()
{
FrameRendering?.Invoke(this, e);
}
if (IsInitialized)
throw new ArtemisCoreException("Cannot initialize the core as it is already initialized.");
protected virtual void OnFrameRendered(FrameRenderedEventArgs e)
{
FrameRendered?.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();
}
private void ApplyLoggingLevel()
@ -98,131 +103,11 @@ internal class CoreService : ICoreService
}
}
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<string?, Exception> 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();
}
public event EventHandler? Initialized;
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<FrameRenderingEventArgs>? FrameRendering;
public event EventHandler<FrameRenderedEventArgs>? FrameRendered;
}

View File

@ -1,22 +1,252 @@
using System.Linq;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
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 IRgbService _rgbService;
private readonly ILogger _logger;
private readonly IPluginManagementService _pluginManagementService;
private readonly IDeviceRepository _deviceRepository;
private readonly Lazy<IRenderService> _renderService;
private readonly List<ArtemisDevice> _enabledDevices = new();
private readonly List<ArtemisDevice> _devices = new();
public DeviceService(IRgbService rgbService)
public DeviceService(ILogger logger, IPluginManagementService pluginManagementService, IDeviceRepository deviceRepository, Lazy<IRenderService> renderService)
{
_rgbService = rgbService;
_logger = logger;
_pluginManagementService = pluginManagementService;
_deviceRepository = deviceRepository;
_renderService = renderService;
EnabledDevices = new ReadOnlyCollection<ArtemisDevice>(_enabledDevices);
Devices = new ReadOnlyCollection<ArtemisDevice>(_devices);
RenderScale.RenderScaleMultiplierChanged += RenderScaleOnRenderScaleMultiplierChanged;
}
public IReadOnlyCollection<ArtemisDevice> EnabledDevices { get; }
public IReadOnlyCollection<ArtemisDevice> Devices { get; }
/// <inheritdoc />
public void IdentifyDevice(ArtemisDevice device)
{
BlinkDevice(device, 0);
}
/// <inheritdoc />
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<ArtemisDevice> 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<Exception> 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<ArtemisDevice> 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;
}
}
/// <inheritdoc />
public void RemoveDeviceProvider(DeviceProvider deviceProvider)
{
_logger.Verbose("[RemoveDeviceProvider] Pausing rendering to remove {DeviceProvider}", deviceProvider.GetType().Name);
IRGBDeviceProvider rgbDeviceProvider = deviceProvider.RgbDeviceProvider;
List<ArtemisDevice> 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;
}
}
/// <inheritdoc />
public void AutoArrangeDevices()
{
SurfaceArrangement surfaceArrangement = SurfaceArrangement.GetDefaultArrangement();
surfaceArrangement.Arrange(_devices);
foreach (ArtemisDevice artemisDevice in _devices)
artemisDevice.ApplyDefaultCategories();
SaveDevices();
}
/// <inheritdoc />
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();
}
/// <inheritdoc />
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();
}
/// <inheritdoc />
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();
}
/// <inheritdoc />
public void SaveDevice(ArtemisDevice artemisDevice)
{
artemisDevice.ApplyToEntity();
artemisDevice.ApplyToRgbDevice();
_deviceRepository.Save(artemisDevice.DeviceEntity);
UpdateLeds();
}
/// <inheritdoc />
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(_rgbService.Surface, device.Leds.Select(l => l.RgbLed))
ListLedGroup ledGroup = new(surface, device.Leds.Select(l => l.RgbLed))
{
Brush = new SolidColorBrush(new Color(255, 255, 255)),
ZIndex = 999
@ -36,9 +266,81 @@ internal class DeviceService : IDeviceService
}
});
}
public void IdentifyDevice(ArtemisDevice device)
private void CalculateRenderProperties()
{
BlinkDevice(device, 0);
foreach (ArtemisDevice artemisDevice in Devices)
artemisDevice.CalculateRenderProperties();
UpdateLeds();
}
private void UpdateLeds()
{
OnLedsChanged();
}
private void RenderScaleOnRenderScaleMultiplierChanged(object? sender, EventArgs e)
{
CalculateRenderProperties();
}
#region Events
/// <inheritdoc />
public event EventHandler<DeviceEventArgs>? DeviceAdded;
/// <inheritdoc />
public event EventHandler<DeviceEventArgs>? DeviceRemoved;
/// <inheritdoc />
public event EventHandler<DeviceEventArgs>? DeviceEnabled;
/// <inheritdoc />
public event EventHandler<DeviceEventArgs>? DeviceDisabled;
/// <inheritdoc />
public event EventHandler<DeviceProviderEventArgs>? DeviceProviderAdded;
/// <inheritdoc />
public event EventHandler<DeviceProviderEventArgs>? DeviceProviderRemoved;
/// <inheritdoc />
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
}

View File

@ -34,4 +34,13 @@ public class ArtemisKeyboardKeyEventArgs : EventArgs
/// Gets the modifiers that are pressed
/// </summary>
public KeyboardModifierKey Modifiers { get; }
/// <summary>
/// Creates a hotkey matching the event.
/// </summary>
/// <returns>The resulting hotkey.</returns>
public Hotkey ToHotkey()
{
return new Hotkey {Key = Key, Modifiers = Modifiers};
}
}

View File

@ -9,19 +9,19 @@ namespace Artemis.Core.Services;
internal class InputService : IInputService
{
private readonly ILogger _logger;
private readonly IRgbService _rgbService;
private readonly IDeviceService _deviceService;
private ArtemisDevice? _firstKeyboard;
private ArtemisDevice? _firstMouse;
private int _keyboardCount;
private int _mouseCount;
public InputService(ILogger logger, IRgbService rgbService)
public InputService(ILogger logger, IDeviceService deviceService)
{
_logger = logger;
_rgbService = rgbService;
_deviceService = deviceService;
_rgbService.DeviceAdded += RgbServiceOnDevicesModified;
_rgbService.DeviceRemoved += RgbServiceOnDevicesModified;
_deviceService.DeviceAdded += DeviceServiceOnDevicesModified;
_deviceService.DeviceRemoved += DeviceServiceOnDevicesModified;
BustIdentifierCache();
}
@ -157,7 +157,7 @@ internal class InputService : IInputService
_logger.Debug("Stop identifying device {device}", _identifyingDevice);
_identifyingDevice = null;
_rgbService.SaveDevices();
_deviceService.SaveDevices();
BustIdentifierCache();
}
@ -209,7 +209,7 @@ internal class InputService : IInputService
public void BustIdentifierCache()
{
_deviceCache.Clear();
_devices = _rgbService.EnabledDevices.Where(d => d.InputIdentifiers.Any()).ToList();
_devices = _deviceService.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 RgbServiceOnDevicesModified(object? sender, DeviceEventArgs args)
private void DeviceServiceOnDevicesModified(object? sender, DeviceEventArgs args)
{
_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);
_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);
BustIdentifierCache();
}

View File

@ -12,21 +12,6 @@ public interface ICoreService : IArtemisService
/// </summary>
bool IsInitialized { get; }
/// <summary>
/// The time the last frame took to render
/// </summary>
TimeSpan FrameTime { get; }
/// <summary>
/// The amount of frames rendered each second
/// </summary>
public int FrameRate { get; }
/// <summary>
/// Gets or sets whether profiles are rendered each frame by calling their Render method
/// </summary>
bool ProfileRenderingDisabled { get; set; }
/// <summary>
/// Gets a boolean indicating whether Artemis is running in an elevated environment (admin permissions)
/// </summary>
@ -41,14 +26,4 @@ public interface ICoreService : IArtemisService
/// Occurs the core has finished initializing
/// </summary>
event EventHandler Initialized;
/// <summary>
/// Occurs whenever a frame is rendering, after modules have rendered
/// </summary>
event EventHandler<FrameRenderingEventArgs> FrameRendering;
/// <summary>
/// Occurs whenever a frame is finished rendering and the render pipeline is closed
/// </summary>
event EventHandler<FrameRenderedEventArgs> FrameRendered;
}

View File

@ -1,13 +1,109 @@
namespace Artemis.Core.Services;
using System;
using System.Collections.Generic;
using Artemis.Core.DeviceProviders;
namespace Artemis.Core.Services;
/// <summary>
/// A service that allows you manage an <see cref="ArtemisDevice" />
/// </summary>
public interface IDeviceService : IArtemisService
{
/// <summary>
/// Gets a read-only collection containing all enabled devices
/// </summary>
IReadOnlyCollection<ArtemisDevice> EnabledDevices { get; }
/// <summary>
/// Gets a read-only collection containing all registered devices
/// </summary>
IReadOnlyCollection<ArtemisDevice> Devices { get; }
/// <summary>
/// Identifies the device by making it blink white 5 times
/// </summary>
/// <param name="device"></param>
void IdentifyDevice(ArtemisDevice device);
/// <summary>
/// Adds the given device provider and its devices.
/// </summary>
/// <param name="deviceProvider"></param>
void AddDeviceProvider(DeviceProvider deviceProvider);
/// <summary>
/// Removes the given device provider and its devices.
/// </summary>
/// <param name="deviceProvider"></param>
void RemoveDeviceProvider(DeviceProvider deviceProvider);
/// <summary>
/// Applies auto-arranging logic to the surface
/// </summary>
void AutoArrangeDevices();
/// <summary>
/// Apples the provided <see cref="ArtemisLayout" /> to the provided <see cref="ArtemisDevice" />
/// </summary>
/// <param name="device"></param>
/// <param name="layout"></param>
void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout? layout);
/// <summary>
/// Enables the provided device
/// </summary>
/// <param name="device">The device to enable</param>
void EnableDevice(ArtemisDevice device);
/// <summary>
/// Disables the provided device
/// </summary>
/// <param name="device">The device to disable</param>
void DisableDevice(ArtemisDevice device);
/// <summary>
/// Saves the configuration of the provided device to persistent storage
/// </summary>
/// <param name="artemisDevice"></param>
void SaveDevice(ArtemisDevice artemisDevice);
/// <summary>
/// Saves the configuration of all current devices to persistent storage
/// </summary>
void SaveDevices();
/// <summary>
/// Occurs when a single device was added.
/// </summary>
event EventHandler<DeviceEventArgs> DeviceAdded;
/// <summary>
/// Occurs when a single device was removed.
/// </summary>
event EventHandler<DeviceEventArgs> DeviceRemoved;
/// <summary>
/// Occurs when a single device was disabled
/// </summary>
event EventHandler<DeviceEventArgs> DeviceEnabled;
/// <summary>
/// Occurs when a single device was disabled.
/// </summary>
event EventHandler<DeviceEventArgs> DeviceDisabled;
/// <summary>
/// Occurs when a device provider was added.
/// </summary>
event EventHandler<DeviceProviderEventArgs> DeviceProviderAdded;
/// <summary>
/// Occurs when a device provider was removed.
/// </summary>
event EventHandler<DeviceProviderEventArgs> DeviceProviderRemoved;
/// <summary>
/// Occurs when the surface has had modifications to its LED collection
/// </summary>
event EventHandler LedsChanged;
}

View File

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using Artemis.Core.Services.Core;
using Artemis.Core.SkiaSharp;
using RGB.NET.Core;
namespace Artemis.Core.Services;
/// <summary>
/// Represents a service that manages the render loop and renderers.
/// </summary>
public interface IRenderService : IArtemisService
{
/// <summary>
/// Gets the graphics context to be used for rendering
/// </summary>
IManagedGraphicsContext? GraphicsContext { get; }
/// <summary>
/// Gets the RGB surface to which is being rendered.
/// </summary>
RGBSurface Surface { get; }
/// <summary>
/// Gets a list of registered renderers.
/// </summary>
List<IRenderer> Renderers { get; }
/// <summary>
/// Gets or sets a boolean indicating whether rendering is paused.
/// </summary>
bool IsPaused { get; set; }
/// <summary>
/// The time the last frame took to render
/// </summary>
TimeSpan FrameTime { get; }
/// <summary>
/// The amount of frames rendered each second
/// </summary>
public int FrameRate { get; }
/// <summary>
/// Initializes the render service and starts rendering.
/// </summary>
void Initialize();
/// <summary>
/// Occurs whenever a frame is rendering, after modules have rendered
/// </summary>
event EventHandler<FrameRenderingEventArgs> FrameRendering;
/// <summary>
/// Occurs whenever a frame is finished rendering and the render pipeline is closed
/// </summary>
event EventHandler<FrameRenderedEventArgs> FrameRendered;
}

View File

@ -1,168 +0,0 @@
using System;
using System.Collections.Generic;
using Artemis.Core.SkiaSharp;
using RGB.NET.Core;
namespace Artemis.Core.Services;
/// <summary>
/// A service that allows you to manage the <see cref="RGBSurface" /> and its contents
/// </summary>
public interface IRgbService : IArtemisService, IDisposable
{
/// <summary>
/// Gets a read-only collection containing all enabled devices
/// </summary>
IReadOnlyCollection<ArtemisDevice> EnabledDevices { get; }
/// <summary>
/// Gets a read-only collection containing all registered devices
/// </summary>
IReadOnlyCollection<ArtemisDevice> Devices { get; }
/// <summary>
/// Gets a dictionary containing all <see cref="ArtemisLed" />s on the surface with their corresponding RGB.NET
/// <see cref="Led" /> as key
/// </summary>
IReadOnlyDictionary<Led, ArtemisLed> LedMap { get; }
/// <summary>
/// Gets or sets the RGB surface rendering is performed on
/// </summary>
RGBSurface Surface { get; set; }
/// <summary>
/// Gets or sets whether rendering should be paused
/// </summary>
bool IsRenderPaused { get; }
/// <summary>
/// Gets a boolean indicating whether the render pipeline is open
/// </summary>
bool RenderOpen { get; }
/// <summary>
/// Gets or sets a boolean indicating whether to flush the RGB.NET LEDs during next update
/// </summary>
bool FlushLeds { get; set; }
/// <summary>
/// Opens the render pipeline
/// </summary>
SKTexture OpenRender();
/// <summary>
/// Closes the render pipeline
/// </summary>
void CloseRender();
/// <summary>
/// Applies the current value of the <c>Core.PreferredGraphicsContext</c> setting to the graphics context.
/// </summary>
/// <param name="forceSoftware">A boolean to indicate whether or not to force the graphics context to software mode.</param>
void ApplyPreferredGraphicsContext(bool forceSoftware);
/// <summary>
/// Updates the graphics context to the provided <paramref name="managedGraphicsContext"></paramref>.
/// <para>Note: The old graphics context will be used until the next frame starts rendering and is disposed afterwards.</para>
/// </summary>
/// <param name="managedGraphicsContext">
/// The new managed graphics context. If <see langword="null" />, software rendering
/// is used.
/// </param>
void UpdateGraphicsContext(IManagedGraphicsContext? managedGraphicsContext);
/// <summary>
/// Adds the given device provider to the <see cref="Surface" />
/// </summary>
/// <param name="deviceProvider"></param>
void AddDeviceProvider(IRGBDeviceProvider deviceProvider);
/// <summary>
/// Removes the given device provider from the <see cref="Surface" />
/// </summary>
/// <param name="deviceProvider"></param>
void RemoveDeviceProvider(IRGBDeviceProvider deviceProvider);
/// <summary>
/// Applies auto-arranging logic to the surface
/// </summary>
void AutoArrangeDevices();
/// <summary>
/// Applies the best available layout for the given <see cref="ArtemisDevice" />
/// </summary>
/// <param name="device">The device to apply the best available layout to</param>
/// <returns>The layout that was applied to the device</returns>
ArtemisLayout? ApplyBestDeviceLayout(ArtemisDevice device);
/// <summary>
/// Apples the provided <see cref="ArtemisLayout" /> to the provided <see cref="ArtemisDevice" />
/// </summary>
/// <param name="device"></param>
/// <param name="layout"></param>
void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout);
/// <summary>
/// Attempts to retrieve the <see cref="ArtemisDevice" /> that corresponds the provided RGB.NET
/// <see cref="IRGBDevice" />
/// </summary>
/// <param name="rgbDevice">
/// The RGB.NET <see cref="IRGBDevice" /> to find the corresponding <see cref="ArtemisDevice" />
/// for
/// </param>
/// <returns>If found, the corresponding <see cref="ArtemisDevice" />; otherwise <see langword="null" />.</returns>
ArtemisDevice? GetDevice(IRGBDevice rgbDevice);
/// <summary>
/// Attempts to retrieve the <see cref="ArtemisLed" /> that corresponds the provided RGB.NET <see cref="Led" />
/// </summary>
/// <param name="led">The RGB.NET <see cref="Led" /> to find the corresponding <see cref="ArtemisLed" /> for </param>
/// <returns>If found, the corresponding <see cref="ArtemisLed" />; otherwise <see langword="null" />.</returns>
ArtemisLed? GetLed(Led led);
/// <summary>
/// Saves the configuration of the provided device to persistent storage
/// </summary>
/// <param name="artemisDevice"></param>
void SaveDevice(ArtemisDevice artemisDevice);
/// <summary>
/// Saves the configuration of all current devices to persistent storage
/// </summary>
void SaveDevices();
/// <summary>
/// Enables the provided device
/// </summary>
/// <param name="device">The device to enable</param>
void EnableDevice(ArtemisDevice device);
/// <summary>
/// Disables the provided device
/// </summary>
/// <param name="device">The device to disable</param>
void DisableDevice(ArtemisDevice device);
/// <summary>
/// Pauses or resumes rendering, method won't return until the current frame finished rendering
/// </summary>
/// <param name="paused"></param>
/// <returns><see langword="true" /> if the pause state was changed; otherwise <see langword="false" />.</returns>
bool SetRenderPaused(bool paused);
/// <summary>
/// Occurs when a single device was added
/// </summary>
event EventHandler<DeviceEventArgs> DeviceAdded;
/// <summary>
/// Occurs when a single device was removed
/// </summary>
event EventHandler<DeviceEventArgs> DeviceRemoved;
/// <summary>
/// Occurs when the surface has had modifications to its LED collection
/// </summary>
event EventHandler LedsChanged;
}

View File

@ -0,0 +1,251 @@
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<Exception> _updateExceptions = new();
private readonly ILogger _logger;
private readonly IDeviceService _deviceService;
private readonly LazyEnumerable<IGraphicsContextProvider> _graphicsContextProviders;
private readonly PluginSetting<int> _targetFrameRateSetting;
private readonly PluginSetting<double> _renderScaleSetting;
private readonly PluginSetting<string> _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<IGraphicsContextProvider> 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);
}
/// <inheritdoc />
public IManagedGraphicsContext? GraphicsContext { get; private set; }
/// <inheritdoc />
public RGBSurface Surface => _surfaceManager.Surface;
/// <inheritdoc />
public List<IRenderer> Renderers { get; } = new();
/// <inheritdoc />
public bool IsPaused
{
get => _surfaceManager.IsPaused;
set => _surfaceManager.SetPaused(value);
}
/// <inheritdoc />
public int FrameRate { get; private set; }
/// <inheritdoc />
public TimeSpan FrameTime { get; private set; }
/// <inheritdoc />
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);
}
}
/// <inheritdoc />
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<IGraphicsContextProvider> 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<string?, Exception> 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<ArtemisDevice> {e.Device});
}
private void DeviceServiceOnDeviceDisabled(object? sender, DeviceEventArgs e)
{
_surfaceManager.RemoveDevices(new List<ArtemisDevice> {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;
}
/// <inheritdoc />
public void Dispose()
{
IsPaused = true;
_surfaceManager.Dispose();
}
/// <inheritdoc />
public event EventHandler<FrameRenderingEventArgs>? FrameRendering;
/// <inheritdoc />
public event EventHandler<FrameRenderedEventArgs>? FrameRendered;
/// <inheritdoc />
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);
}
}

View File

@ -1,613 +0,0 @@
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;
/// <summary>
/// Provides wrapped access the RGB.NET
/// </summary>
internal class RgbService : IRgbService
{
private readonly ILogger _logger;
private readonly IPluginManagementService _pluginManagementService;
private readonly IDeviceRepository _deviceRepository;
private readonly LazyEnumerable<IGraphicsContextProvider> _graphicsContextProviders;
private readonly PluginSetting<string> _preferredGraphicsContext;
private readonly PluginSetting<double> _renderScaleSetting;
private readonly PluginSetting<int> _targetFrameRateSetting;
private readonly List<ArtemisDevice> _devices;
private readonly List<ArtemisDevice> _enabledDevices;
private readonly SKTextureBrush _textureBrush = new(null) {CalculationMode = RenderMode.Absolute};
private Dictionary<Led, ArtemisLed> _ledMap;
private ListLedGroup? _surfaceLedGroup;
private SKTexture? _texture;
public RgbService(ILogger logger,
ISettingsService settingsService,
IPluginManagementService pluginManagementService,
IDeviceRepository deviceRepository,
LazyEnumerable<IGraphicsContextProvider> 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<ArtemisDevice>();
_devices = new List<ArtemisDevice>();
_ledMap = new Dictionary<Led, ArtemisLed>();
EnabledDevices = new ReadOnlyCollection<ArtemisDevice>(_enabledDevices);
Devices = new ReadOnlyCollection<ArtemisDevice>(_devices);
LedMap = new ReadOnlyDictionary<Led, ArtemisLed>(_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<Led, ArtemisLed>(_devices.SelectMany(d => d.Leds).ToDictionary(l => l.RgbLed));
LedMap = new ReadOnlyDictionary<Led, ArtemisLed>(_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<ArtemisDevice> EnabledDevices { get; }
public IReadOnlyCollection<ArtemisDevice> Devices { get; }
public IReadOnlyDictionary<Led, ArtemisLed> LedMap { get; private set; }
public RGBSurface Surface { get; set; }
public bool IsRenderPaused { get; set; }
public bool RenderOpen { get; private set; }
/// <inheritdoc />
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<ArtemisDevice> 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<Exception> 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<ArtemisDevice> 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<DeviceEventArgs>? DeviceAdded;
public event EventHandler<DeviceEventArgs>? 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<IGraphicsContextProvider> 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
}

View File

@ -41,6 +41,11 @@ public interface IProfileService : IArtemisService
/// Gets or sets a value indicating whether the currently focused profile should receive updates.
/// </summary>
bool UpdateFocusProfile { get; set; }
/// <summary>
/// Gets or sets whether profiles are rendered each frame by calling their Render method
/// </summary>
bool ProfileRenderingDisabled { get; set; }
/// <summary>
/// Creates a copy of the provided profile configuration.

View File

@ -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,36 +16,37 @@ using SkiaSharp;
namespace Artemis.Core.Services;
internal class ProfileService : IProfileService
internal class ProfileService : IProfileService, IRenderer
{
private readonly ILogger _logger;
private readonly IRgbService _rgbService;
private readonly IProfileCategoryRepository _profileCategoryRepository;
private readonly IPluginManagementService _pluginManagementService;
private readonly IDeviceService _deviceService;
private readonly List<ArtemisKeyboardKeyEventArgs> _pendingKeyboardEvents = new();
private readonly List<ProfileCategory> _profileCategories;
private readonly IProfileRepository _profileRepository;
private readonly List<Exception> _renderExceptions = new();
private readonly List<Exception> _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<ProfileCategory>(_profileCategoryRepository.GetAll().Select(c => new ProfileCategory(c)).OrderBy(c => c.Order));
_rgbService.LedsChanged += RgbServiceOnLedsChanged;
_deviceService.LedsChanged += DeviceServiceOnLedsChanged;
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;
_pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled;
@ -54,11 +55,15 @@ internal class ProfileService : IProfileService
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; }
/// <inheritdoc />
public void UpdateProfiles(double deltaTime)
@ -182,6 +187,21 @@ internal class ProfileService : IProfileService
}
}
}
/// <inheritdoc />
public void Render(SKCanvas canvas, double delta)
{
if (ProfileRenderingDisabled)
return;
UpdateProfiles(delta);
RenderProfiles(canvas);
}
/// <inheritdoc />
public void PostRender(SKTexture texture)
{
}
/// <inheritdoc />
public void LoadProfileConfigurationIcon(ProfileConfiguration profileConfiguration)
@ -235,7 +255,7 @@ internal class ProfileService : IProfileService
throw new ArtemisCoreException($"Cannot find profile named: {profileConfiguration.Name} ID: {profileConfiguration.Entity.ProfileId}");
Profile profile = new(profileConfiguration, profileEntity);
profile.PopulateLeds(_rgbService.EnabledDevices);
profile.PopulateLeds(_deviceService.EnabledDevices);
if (profile.IsFreshImport)
{
@ -523,7 +543,7 @@ internal class ProfileService : IProfileService
/// <inheritdoc />
public void AdaptProfile(Profile profile)
{
List<ArtemisDevice> devices = _rgbService.EnabledDevices.ToList();
List<ArtemisDevice> devices = _deviceService.EnabledDevices.ToList();
foreach (Layer layer in profile.GetAllLayers())
layer.Adapter.Adapt(devices);
@ -552,7 +572,7 @@ internal class ProfileService : IProfileService
foreach (ProfileConfiguration profileConfiguration in ProfileConfigurations)
{
if (profileConfiguration.Profile == null) continue;
profileConfiguration.Profile.PopulateLeds(_rgbService.EnabledDevices);
profileConfiguration.Profile.PopulateLeds(_deviceService.EnabledDevices);
if (!profileConfiguration.Profile.IsFreshImport) continue;
_logger.Debug("Profile is a fresh import, adapting to surface - {profile}", profileConfiguration.Profile);
@ -573,7 +593,7 @@ internal class ProfileService : IProfileService
}
}
private void RgbServiceOnLedsChanged(object? sender, EventArgs e)
private void DeviceServiceOnLedsChanged(object? sender, EventArgs e)
{
ActiveProfilesPopulateLeds();
}

View File

@ -0,0 +1,35 @@
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
);
}
}

View File

@ -156,28 +156,4 @@ 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
}

View File

@ -27,8 +27,8 @@ namespace Artemis.UI.Shared;
/// </summary>
public class DeviceVisualizer : Control
{
internal static readonly Dictionary<ArtemisDevice, RenderTargetBitmap?> BitmapCache = new();
private readonly ICoreService _coreService;
internal static readonly Dictionary<string, RenderTargetBitmap?> BitmapCache = new();
private readonly IRenderService _renderService;
private readonly List<DeviceVisualizerLed> _deviceVisualizerLeds;
private Rect _deviceBounds;
@ -40,7 +40,7 @@ public class DeviceVisualizer : Control
/// <inheritdoc />
public DeviceVisualizer()
{
_coreService = UI.Locator.Resolve<ICoreService>();
_renderService = UI.Locator.Resolve<IRenderService>();
_deviceVisualizerLeds = new List<DeviceVisualizerLed>();
PointerReleased += OnPointerReleased;
@ -195,16 +195,6 @@ 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);
@ -251,7 +241,6 @@ public class DeviceVisualizer : Control
{
if (Device != null)
{
Device.RgbDevice.PropertyChanged -= DevicePropertyChanged;
Device.DeviceUpdated -= DeviceUpdated;
}
@ -261,7 +250,7 @@ public class DeviceVisualizer : Control
/// <inheritdoc />
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
_coreService.FrameRendered += OnFrameRendered;
_renderService.FrameRendered += OnFrameRendered;
base.OnAttachedToLogicalTree(e);
}
@ -269,7 +258,7 @@ public class DeviceVisualizer : Control
/// <inheritdoc />
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
_coreService.FrameRendered -= OnFrameRendered;
_renderService.FrameRendered -= OnFrameRendered;
base.OnDetachedFromLogicalTree(e);
}
@ -283,7 +272,6 @@ public class DeviceVisualizer : Control
if (_oldDevice != null)
{
_oldDevice.RgbDevice.PropertyChanged -= DevicePropertyChanged;
_oldDevice.DeviceUpdated -= DeviceUpdated;
}
@ -294,7 +282,6 @@ public class DeviceVisualizer : Control
_deviceBounds = MeasureDevice();
_loading = true;
Device.RgbDevice.PropertyChanged += DevicePropertyChanged;
Device.DeviceUpdated += DeviceUpdated;
// Create all the LEDs
@ -321,12 +308,15 @@ public class DeviceVisualizer : Control
private RenderTargetBitmap? GetDeviceImage(ArtemisDevice device)
{
if (BitmapCache.TryGetValue(device, out RenderTargetBitmap? existingBitmap))
string? path = device.Layout?.Image?.LocalPath;
if (path == null)
return null;
if (BitmapCache.TryGetValue(path, out RenderTargetBitmap? existingBitmap))
return existingBitmap;
if (device.Layout?.Image == null || !File.Exists(device.Layout.Image.LocalPath))
if (!File.Exists(path))
{
BitmapCache[device] = null;
BitmapCache[path] = null;
return null;
}
@ -335,7 +325,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(device.Layout.Image.LocalPath);
using Bitmap bitmap = new(path);
using Bitmap scaledBitmap = bitmap.CreateScaledBitmap(renderTargetBitmap.PixelSize);
context.DrawImage(scaledBitmap, new Rect(scaledBitmap.Size));
@ -345,7 +335,7 @@ public class DeviceVisualizer : Control
deviceVisualizerLed.DrawBitmap(context, 2 * device.Scale);
}
BitmapCache[device] = renderTargetBitmap;
BitmapCache[path] = renderTargetBitmap;
return renderTargetBitmap;
}

View File

@ -11,7 +11,12 @@ public interface IMainWindowService : IArtemisSharedUIService
/// Gets a boolean indicating whether the main window is currently open
/// </summary>
bool IsMainWindowOpen { get; }
/// <summary>
/// Gets a boolean indicating whether the main window is currently focused
/// </summary>
bool IsMainWindowFocused { get; }
/// <summary>
/// Sets up the main window provider that controls the state of the main window
/// </summary>

View File

@ -10,6 +10,9 @@ internal class MainWindowService : IMainWindowService
/// <inheritdoc />
public bool IsMainWindowOpen { get; private set; }
/// <inheritdoc />
public bool IsMainWindowFocused { get; private set; }
protected virtual void OnMainWindowOpened()
{
MainWindowOpened?.Invoke(this, EventArgs.Empty);
@ -24,11 +27,13 @@ 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()

View File

@ -1,4 +1,6 @@
using System;
using Artemis.Core;
using Avalonia.Input;
using Material.Icons;
namespace Artemis.UI.Shared.Services.ProfileEditor;
@ -43,6 +45,11 @@ public interface IToolViewModel : IDisposable
/// Gets the tooltip which this tool should show in the toolbar.
/// </summary>
public string ToolTip { get; }
/// <summary>
/// Gets the keyboard hotkey that activates the tool.
/// </summary>
Hotkey? Hotkey { get; }
}
/// <inheritdoc cref="IToolViewModel" />
@ -98,5 +105,8 @@ public abstract class ToolViewModel : ActivatableViewModelBase, IToolViewModel
/// <inheritdoc />
public abstract string ToolTip { get; }
/// <inheritdoc />
public abstract Hotkey? Hotkey { get; }
#endregion
}

View File

@ -21,6 +21,7 @@ internal class ProfileEditorService : IProfileEditorService
private readonly ILayerBrushService _layerBrushService;
private readonly BehaviorSubject<ILayerProperty?> _layerPropertySubject = new(null);
private readonly ILogger _logger;
private readonly IDeviceService _deviceService;
private readonly IModuleService _moduleService;
private readonly BehaviorSubject<int> _pixelsPerSecondSubject = new(120);
private readonly BehaviorSubject<bool> _playingSubject = new(false);
@ -28,7 +29,6 @@ internal class ProfileEditorService : IProfileEditorService
private readonly Dictionary<ProfileConfiguration, ProfileEditorHistory> _profileEditorHistories = new();
private readonly BehaviorSubject<RenderProfileElement?> _profileElementSubject = new(null);
private readonly IProfileService _profileService;
private readonly IRgbService _rgbService;
private readonly SourceList<ILayerPropertyKeyframe> _selectedKeyframes;
private readonly BehaviorSubject<bool> _suspendedEditingSubject = new(false);
private readonly BehaviorSubject<TimeSpan> _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(_rgbService.EnabledDevices.SelectMany(d => d.Leds));
layer.AddLeds(_deviceService.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(_rgbService.EnabledDevices.SelectMany(d => d.Leds));
layer.AddLeds(_deviceService.EnabledDevices.SelectMany(d => d.Leds));
ExecuteCommand(new AddProfileElement(layer, target, 0));
return layer;

View File

@ -22,10 +22,11 @@ public static class UI
static UI()
{
KeyBindingsEnabled = InputElement.GotFocusEvent.Raised.Select(e => e.Item2.Source is not TextBox).StartWith(true);
CurrentKeyBindingsEnabled = InputElement.GotFocusEvent.Raised.Select(e => e.Item2.Source is not TextBox).StartWith(true);
CurrentKeyBindingsEnabled.Subscribe(b => KeyBindingsEnabled = b);
MicaEnabled = MicaEnabledSubject.AsObservable();
}
/// <summary>
/// Gets the current IoC locator.
/// </summary>
@ -36,10 +37,15 @@ public static class UI
/// </summary>
public static IClipboard Clipboard { get; set; } = null!;
/// <summary>
/// Gets an observable boolean indicating whether hotkeys are to be disabled.
/// </summary>
public static IObservable<bool> CurrentKeyBindingsEnabled { get; }
/// <summary>
/// Gets a boolean indicating whether hotkeys are to be disabled.
/// </summary>
public static IObservable<bool> KeyBindingsEnabled { get; }
public static bool KeyBindingsEnabled { get; private set; }
/// <summary>
/// Gets a boolean indicating whether the Mica effect should be enabled.

View File

@ -14,7 +14,7 @@ namespace Artemis.UI.Screens.Debugger.Performance;
public class PerformanceDebugViewModel : ActivatableViewModelBase
{
private readonly ICoreService _coreService;
private readonly IRenderService _renderService;
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(ICoreService coreService, IPluginManagementService pluginManagementService)
public PerformanceDebugViewModel(IRenderService renderService, IPluginManagementService pluginManagementService)
{
_coreService = coreService;
_renderService = renderService;
_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";
_coreService.FrameRendered += CoreServiceOnFrameRendered;
_renderService.FrameRendered += RenderServiceOnFrameRendered;
}
private void HandleDeactivation()
{
_coreService.FrameRendered -= CoreServiceOnFrameRendered;
_renderService.FrameRendered -= RenderServiceOnFrameRendered;
}
private void PopulateItems()
@ -116,9 +116,9 @@ public class PerformanceDebugViewModel : ActivatableViewModelBase
viewModel.Update();
}
private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
private void RenderServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
{
CurrentFps = _coreService.FrameRate;
CurrentFps = _renderService.FrameRate;
SKImageInfo bitmapInfo = e.Texture.ImageInfo;
RenderHeight = bitmapInfo.Height;

View File

@ -11,7 +11,7 @@ namespace Artemis.UI.Screens.Debugger.Render;
public class RenderDebugViewModel : ActivatableViewModelBase
{
private readonly ICoreService _coreService;
private readonly IRenderService _renderService;
private double _currentFps;
private Bitmap? _currentFrame;
@ -20,9 +20,9 @@ public class RenderDebugViewModel : ActivatableViewModelBase
private int _renderHeight;
private int _renderWidth;
public RenderDebugViewModel(ICoreService coreService)
public RenderDebugViewModel(IRenderService renderService)
{
_coreService = coreService;
_renderService = renderService;
DisplayName = "Rendering";
@ -66,17 +66,17 @@ public class RenderDebugViewModel : ActivatableViewModelBase
private void HandleActivation()
{
Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software";
_coreService.FrameRendered += CoreServiceOnFrameRendered;
_renderService.FrameRendered += RenderServiceOnFrameRendered;
}
private void HandleDeactivation()
{
_coreService.FrameRendered -= CoreServiceOnFrameRendered;
_renderService.FrameRendered -= RenderServiceOnFrameRendered;
}
private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
private void RenderServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
{
CurrentFps = _coreService.FrameRate;
CurrentFps = _renderService.FrameRate;
using SKImage skImage = e.Texture.Surface.Snapshot();
SKImageInfo bitmapInfo = e.Texture.ImageInfo;

View File

@ -17,20 +17,16 @@ 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, IRgbService rgbService)
public DeviceDetectInputViewModel(ArtemisDevice device, IInputService inputService, INotificationService notificationService, IRenderService renderService)
{
_inputService = inputService;
_notificationService = notificationService;
_rgbService = rgbService;
Device = device;
// Create a LED group way at the top
_ledGroup = new ListLedGroup(_rgbService.Surface, Device.Leds.Select(l => l.RgbLed))
ListLedGroup ledGroup = new(renderService.Surface, Device.Leds.Select(l => l.RgbLed))
{
Brush = new SolidColorBrush(new Color(255, 255, 0)),
ZIndex = 999
@ -46,7 +42,7 @@ public class DeviceDetectInputViewModel : ContentDialogViewModelBase
Disposable.Create(() =>
{
_inputService.StopIdentify();
_ledGroup.Detach();
ledGroup.Detach();
}).DisposeWith(disposables);
});
}

View File

@ -17,7 +17,7 @@ public class DevicePropertiesViewModel : DialogViewModelBase<object>
private readonly IDeviceVmFactory _deviceVmFactory;
private ArtemisDevice _device;
public DevicePropertiesViewModel(ArtemisDevice device, ICoreService coreService, IRgbService rgbService, IDeviceVmFactory deviceVmFactory)
public DevicePropertiesViewModel(ArtemisDevice device, IRenderService renderService, IDeviceService deviceService, IDeviceVmFactory deviceVmFactory)
{
_deviceVmFactory = deviceVmFactory;
_device = device;
@ -28,10 +28,15 @@ public class DevicePropertiesViewModel : DialogViewModelBase<object>
AddTabs();
this.WhenActivated(d =>
{
rgbService.DeviceAdded += RgbServiceOnDeviceAdded;
rgbService.DeviceRemoved += RgbServiceOnDeviceRemoved;
coreService.FrameRendering += CoreServiceOnFrameRendering;
Disposable.Create(() => coreService.FrameRendering -= CoreServiceOnFrameRendering).DisposeWith(d);
deviceService.DeviceAdded += DeviceServiceOnDeviceAdded;
deviceService.DeviceRemoved += DeviceServiceOnDeviceRemoved;
renderService.FrameRendering += RenderServiceOnFrameRendering;
Disposable.Create(() =>
{
deviceService.DeviceAdded -= DeviceServiceOnDeviceAdded;
deviceService.DeviceRemoved -= DeviceServiceOnDeviceRemoved;
renderService.FrameRendering -= RenderServiceOnFrameRendering;
}).DisposeWith(d);
});
ClearSelectedLeds = ReactiveCommand.Create(ExecuteClearSelectedLeds);
@ -47,7 +52,7 @@ public class DevicePropertiesViewModel : DialogViewModelBase<object>
public ObservableCollection<ActivatableViewModelBase> Tabs { get; }
public ReactiveCommand<Unit, Unit> ClearSelectedLeds { get; }
private void RgbServiceOnDeviceAdded(object? sender, DeviceEventArgs e)
private void DeviceServiceOnDeviceAdded(object? sender, DeviceEventArgs e)
{
if (e.Device.Identifier != Device.Identifier || Device == e.Device)
return;
@ -56,7 +61,7 @@ public class DevicePropertiesViewModel : DialogViewModelBase<object>
AddTabs();
}
private void RgbServiceOnDeviceRemoved(object? sender, DeviceEventArgs e)
private void DeviceServiceOnDeviceRemoved(object? sender, DeviceEventArgs e)
{
Tabs.Clear();
SelectedLeds.Clear();
@ -76,7 +81,7 @@ public class DevicePropertiesViewModel : DialogViewModelBase<object>
SelectedLeds.Clear();
}
private void CoreServiceOnFrameRendering(object? sender, FrameRenderingEventArgs e)
private void RenderServiceOnFrameRendering(object? sender, FrameRenderingEventArgs e)
{
if (!SelectedLeds.Any())
return;

View File

@ -18,18 +18,15 @@ 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,
IRgbService rgbService)
public DeviceSettingsViewModel(ArtemisDevice device, DevicesTabViewModel devicesTabViewModel, IDeviceService deviceService, IWindowService windowService, IDeviceVmFactory deviceVmFactory)
{
_devicesTabViewModel = devicesTabViewModel;
_deviceService = deviceService;
_windowService = windowService;
_deviceVmFactory = deviceVmFactory;
_rgbService = rgbService;
Device = device;
Type = Device.DeviceType.ToString().Humanize();
@ -87,7 +84,7 @@ public class DeviceSettingsViewModel : ActivatableViewModelBase
.ShowAsync();
if (viewModel.MadeChanges)
_rgbService.SaveDevice(Device);
_deviceService.SaveDevice(Device);
}
private async Task UpdateIsDeviceEnabled(bool value)
@ -103,9 +100,9 @@ public class DeviceSettingsViewModel : ActivatableViewModelBase
value = !await _devicesTabViewModel.ShowDeviceDisableDialog();
if (value)
_rgbService.EnableDevice(Device);
_deviceService.EnableDevice(Device);
else
_rgbService.DisableDevice(Device);
_deviceService.DisableDevice(Device);
this.RaisePropertyChanged(nameof(IsDeviceEnabled));
SaveDevice();
@ -118,6 +115,6 @@ public class DeviceSettingsViewModel : ActivatableViewModelBase
private void SaveDevice()
{
_rgbService.SaveDevice(Device);
_deviceService.SaveDevice(Device);
}
}

View File

@ -20,7 +20,8 @@ namespace Artemis.UI.Screens.Device;
public class DeviceGeneralTabViewModel : ActivatableViewModelBase
{
private readonly ICoreService _coreService;
private readonly IRgbService _rgbService;
private readonly IDeviceService _deviceService;
private readonly IRenderService _renderService;
private readonly IWindowService _windowService;
private readonly List<DeviceCategory> _categories;
@ -39,10 +40,11 @@ public class DeviceGeneralTabViewModel : ActivatableViewModelBase
private SKColor _currentColor;
private bool _displayOnDevices;
public DeviceGeneralTabViewModel(ArtemisDevice device, ICoreService coreService, IRgbService rgbService, IWindowService windowService)
public DeviceGeneralTabViewModel(ArtemisDevice device, ICoreService coreService, IDeviceService deviceService, IRenderService renderService, IWindowService windowService)
{
_coreService = coreService;
_rgbService = rgbService;
_deviceService = deviceService;
_renderService = renderService;
_windowService = windowService;
_categories = new List<DeviceCategory>(device.Categories);
@ -66,11 +68,11 @@ public class DeviceGeneralTabViewModel : ActivatableViewModelBase
this.WhenActivated(d =>
{
_coreService.FrameRendering += OnFrameRendering;
_renderService.FrameRendering += OnFrameRendering;
Disposable.Create(() =>
{
_coreService.FrameRendering -= OnFrameRendering;
_renderService.FrameRendering -= OnFrameRendering;
Apply();
}).DisposeWith(d);
});
@ -191,17 +193,12 @@ public class DeviceGeneralTabViewModel : ActivatableViewModelBase
return;
await Task.Delay(400);
_rgbService.SaveDevice(Device);
_rgbService.ApplyBestDeviceLayout(Device);
_deviceService.SaveDevice(Device);
_deviceService.ApplyDeviceLayout(Device, Device.GetBestDeviceLayout());
}
private void Apply()
{
// TODO: Validation
_coreService.ProfileRenderingDisabled = true;
Thread.Sleep(100);
Device.X = X;
Device.Y = Y;
Device.Scale = Scale;
@ -213,9 +210,7 @@ public class DeviceGeneralTabViewModel : ActivatableViewModelBase
foreach (DeviceCategory deviceCategory in _categories)
Device.Categories.Add(deviceCategory);
_rgbService.SaveDevice(Device);
_coreService.ProfileRenderingDisabled = false;
_deviceService.SaveDevice(Device);
}
public void ApplyScaling()
@ -223,8 +218,7 @@ public class DeviceGeneralTabViewModel : ActivatableViewModelBase
Device.RedScale = RedScale / 100f;
Device.GreenScale = GreenScale / 100f;
Device.BlueScale = BlueScale / 100f;
_rgbService.FlushLeds = true;
Device.RgbDevice.Update(true);
}
public void ResetScaling()
@ -232,6 +226,7 @@ public class DeviceGeneralTabViewModel : ActivatableViewModelBase
RedScale = _initialRedScale * 100;
GreenScale = _initialGreenScale * 100;
BlueScale = _initialBlueScale * 100;
Device.RgbDevice.Update(true);
}
private void OnFrameRendering(object? sender, FrameRenderingEventArgs e)

View File

@ -22,20 +22,13 @@ public class DeviceLayoutTabViewModel : ActivatableViewModelBase
{
private readonly IWindowService _windowService;
private readonly INotificationService _notificationService;
private readonly ICoreService _coreService;
private readonly IRgbService _rgbService;
private readonly IDeviceService _deviceService;
public DeviceLayoutTabViewModel(
IWindowService windowService,
INotificationService notificationService,
ICoreService coreService,
IRgbService rgbService,
ArtemisDevice device)
public DeviceLayoutTabViewModel(IWindowService windowService, INotificationService notificationService, IDeviceService deviceService, ArtemisDevice device)
{
_windowService = windowService;
_notificationService = notificationService;
_coreService = coreService;
_rgbService = rgbService;
_deviceService = deviceService;
Device = device;
DisplayName = "Layout";
@ -44,11 +37,7 @@ public class DeviceLayoutTabViewModel : ActivatableViewModelBase
this.WhenActivated(d =>
{
Device.PropertyChanged += DeviceOnPropertyChanged;
Disposable.Create(() =>
{
Device.PropertyChanged -= DeviceOnPropertyChanged;
}).DisposeWith(d);
Disposable.Create(() => Device.PropertyChanged -= DeviceOnPropertyChanged).DisposeWith(d);
});
}
@ -58,7 +47,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;
@ -68,6 +57,8 @@ public class DeviceLayoutTabViewModel : ActivatableViewModelBase
_notificationService.CreateNotification()
.WithMessage("Cleared imported layout.")
.WithSeverity(NotificationSeverity.Informational);
_deviceService.SaveDevice(Device);
}
public async Task BrowseCustomLayout()
@ -84,6 +75,8 @@ public class DeviceLayoutTabViewModel : ActivatableViewModelBase
.WithTitle("Imported layout")
.WithMessage($"File loaded from {files[0]}")
.WithSeverity(NotificationSeverity.Informational);
_deviceService.SaveDevice(Device);
}
}
@ -160,6 +153,10 @@ public class DeviceLayoutTabViewModel : ActivatableViewModelBase
private void DeviceOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName is nameof(Device.CustomLayoutPath) or nameof(Device.DisableDefaultLayout))
Task.Run(() => _rgbService.ApplyBestDeviceLayout(Device));
{
Task.Run(() => _deviceService.ApplyDeviceLayout(Device, Device.GetBestDeviceLayout()));
this.RaisePropertyChanged(nameof(CustomLayoutPath));
this.RaisePropertyChanged(nameof(HasCustomLayout));
}
}
}

View File

@ -16,17 +16,17 @@ namespace Artemis.UI.Screens.Device;
public class InputMappingsTabViewModel : ActivatableViewModelBase
{
private readonly IInputService _inputService;
private readonly IRgbService _rgbService;
private readonly IDeviceService _deviceService;
private readonly ObservableCollection<ArtemisLed> _selectedLeds;
private ArtemisLed? _selectedLed;
public InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds, IRgbService rgbService, IInputService inputService)
public InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds, IInputService inputService, IDeviceService deviceService)
{
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;
_rgbService.SaveDevice(Device);
_deviceService.SaveDevice(Device);
_selectedLeds.Clear();
UpdateInputMappings();

View File

@ -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.KeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d);
_keyBindingsEnabled = Shared.UI.CurrentKeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d);
});
AddFolder = ReactiveCommand.Create(ExecuteAddFolder);

View File

@ -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.KeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d);
_keyBindingsEnabled = Shared.UI.CurrentKeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d);
// Update timer
Timer updateTimer = new(TimeSpan.FromMilliseconds(60.0 / 1000));

View File

@ -15,12 +15,12 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.Dialogs;
public class LayerHintsDialogViewModel : DialogViewModelBase<bool>
{
private readonly IRgbService _rgbService;
private readonly IDeviceService _deviceService;
private readonly ILayerHintVmFactory _vmFactory;
public LayerHintsDialogViewModel(Layer layer, IRgbService rgbService, ILayerHintVmFactory vmFactory)
public LayerHintsDialogViewModel(Layer layer, IDeviceService deviceService, ILayerHintVmFactory vmFactory)
{
_rgbService = rgbService;
_deviceService = deviceService;
_vmFactory = vmFactory;
Layer = layer;
@ -49,7 +49,7 @@ public class LayerHintsDialogViewModel : DialogViewModelBase<bool>
public void AutoDetermineHints()
{
Layer.Adapter.DetermineHints(_rgbService.EnabledDevices);
Layer.Adapter.DetermineHints(_deviceService.EnabledDevices);
}
public void AddCategoryHint()

View File

@ -14,17 +14,17 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree;
public class FolderTreeItemViewModel : TreeItemViewModel
{
private readonly IRgbService _rgbService;
private readonly IDeviceService _deviceService;
public FolderTreeItemViewModel(TreeItemViewModel? parent,
Folder folder,
IWindowService windowService,
IDeviceService deviceService,
IProfileEditorService profileEditorService,
IRgbService rgbService,
IProfileEditorVmFactory profileEditorVmFactory)
: base(parent, folder, windowService, rgbService, profileEditorService, profileEditorVmFactory)
: base(parent, folder, windowService, deviceService, profileEditorService, profileEditorVmFactory)
{
_rgbService = rgbService;
_deviceService = deviceService;
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(_rgbService.EnabledDevices);
Folder.Profile.PopulateLeds(_deviceService.EnabledDevices);
}
private async Task ExecutePasteInto()

View File

@ -14,17 +14,17 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree;
public class LayerTreeItemViewModel : TreeItemViewModel
{
private readonly IRgbService _rgbService;
private readonly IDeviceService _deviceService;
public LayerTreeItemViewModel(TreeItemViewModel? parent,
Layer layer,
IWindowService windowService,
IProfileEditorService profileEditorService,
IRgbService rgbService,
IDeviceService deviceService,
IProfileEditorVmFactory profileEditorVmFactory)
: base(parent, layer, windowService, rgbService, profileEditorService, profileEditorVmFactory)
: base(parent, layer, windowService, deviceService, profileEditorService, profileEditorVmFactory)
{
_rgbService = rgbService;
_deviceService = deviceService;
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(_rgbService.EnabledDevices);
Layer.Profile.PopulateLeds(_deviceService.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(_rgbService.EnabledDevices);
Layer.Profile.PopulateLeds(_deviceService.EnabledDevices);
}
/// <inheritdoc />

View File

@ -23,8 +23,8 @@ public class ProfileTreeViewModel : TreeItemViewModel
private ObservableAsPropertyHelper<bool>? _keyBindingsEnabled;
private TreeItemViewModel? _selectedChild;
public ProfileTreeViewModel(IWindowService windowService, IRgbService rgbService, IProfileEditorService profileEditorService, IProfileEditorVmFactory profileEditorVmFactory)
: base(null, null, windowService, rgbService, profileEditorService, profileEditorVmFactory)
public ProfileTreeViewModel(IWindowService windowService, IDeviceService deviceService, IProfileEditorService profileEditorService, IProfileEditorVmFactory profileEditorVmFactory)
: base(null, null, windowService, deviceService, 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.KeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d);
_keyBindingsEnabled = Shared.UI.CurrentKeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d);
});
ClearSelection = ReactiveCommand.Create(() => profileEditorService.ChangeCurrentProfileElement(null), this.WhenAnyValue(vm => vm.KeyBindingsEnabled));

View File

@ -28,7 +28,7 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
{
private readonly IProfileEditorVmFactory _profileEditorVmFactory;
private readonly IWindowService _windowService;
private readonly IRgbService _rgbService;
private readonly IDeviceService _deviceService;
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,
IRgbService rgbService,
IDeviceService deviceService,
IProfileEditorService profileEditorService,
IProfileEditorVmFactory profileEditorVmFactory)
{
ProfileEditorService = profileEditorService;
_windowService = windowService;
_rgbService = rgbService;
_deviceService = deviceService;
_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, _rgbService.EnabledDevices.ToList()));
ProfileEditorService.ExecuteCommand(new ApplyAdaptionHints(layer, _deviceService.EnabledDevices.ToList()));
}
private async void UpdateCanPaste(bool isFlyoutOpen)

View File

@ -3,17 +3,21 @@
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">
<StackPanel Orientation="Horizontal">
<Polyline Stroke="{DynamicResource TextFillColorPrimaryBrush}"
<Grid ColumnDefinitions="25,30,*">
<avalonia:MaterialIcon Grid.Column="0" Kind="Check" IsVisible="{CompiledBinding IsEasingModeSelected}" HorizontalAlignment="Left"/>
<Polyline Grid.Column="1"
Stroke="{DynamicResource TextFillColorPrimaryBrush}"
StrokeThickness="1"
Points="{CompiledBinding EasingPoints}"
Stretch="Uniform"
Width="20"
Height="20"
Margin="0 0 10 0" />
<TextBlock Text="{CompiledBinding Description}" />
</StackPanel>
HorizontalAlignment="Left"/>
<TextBlock Grid.Column="2" Text="{CompiledBinding Description}" HorizontalAlignment="Left"/>
</Grid>
</UserControl>

View File

@ -1,8 +1,10 @@
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;
@ -10,11 +12,12 @@ public class TimelineEasingViewModel : ViewModelBase
{
private readonly ILayerPropertyKeyframe _keyframe;
public TimelineEasingViewModel(Easings.Functions easingFunction, ILayerPropertyKeyframe keyframe)
public TimelineEasingViewModel(Easings.Functions easingFunction, ILayerPropertyKeyframe keyframe, ReactiveCommand<Easings.Functions, Unit> selectEasingFunction)
{
_keyframe = keyframe;
EasingFunction = easingFunction;
SelectEasingFunction = selectEasingFunction;
Description = easingFunction.Humanize();
EasingPoints = new List<Point>();
@ -27,6 +30,7 @@ public class TimelineEasingViewModel : ViewModelBase
}
public Easings.Functions EasingFunction { get; }
public ReactiveCommand<Easings.Functions, Unit> SelectEasingFunction { get; }
public List<Point> EasingPoints { get; }
public string Description { get; }
public bool IsEasingModeSelected => _keyframe.EasingFunction == EasingFunction;

View File

@ -3,6 +3,7 @@
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">
@ -34,16 +35,9 @@
<MenuFlyout Opening="FlyoutBase_OnOpening">
<MenuItem Header="Easing" ItemsSource="{Binding EasingViewModels}">
<MenuItem.Styles>
<Style Selector="MenuItem > MenuItem">
<Setter Property="Icon">
<Setter.Value>
<Template>
<CheckBox IsHitTestVisible="False" IsChecked="{Binding IsEasingModeSelected}" />
</Template>
</Setter.Value>
</Setter>
<Setter Property="Command" Value="{Binding $parent[UserControl].DataContext.SelectEasingFunction}" />
<Setter Property="CommandParameter" Value="{Binding EasingFunction}" />
<Style Selector="MenuItem > MenuItem" x:DataType="keyframes:TimelineEasingViewModel">
<Setter Property="Command" Value="{CompiledBinding SelectEasingFunction}" />
<Setter Property="CommandParameter" Value="{CompiledBinding EasingFunction}" />
</Style>
</MenuItem.Styles>
<MenuItem.Icon>

View File

@ -54,10 +54,12 @@ public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineK
Copy = ReactiveCommand.CreateFromTask(ExecuteCopy);
Paste = ReactiveCommand.CreateFromTask(ExecutePaste);
Delete = ReactiveCommand.Create(ExecuteDelete);
SelectEasingFunction = ReactiveCommand.Create<Easings.Functions>(ExecuteSelectEasingFunction);
}
public LayerPropertyKeyframe<T> LayerPropertyKeyframe { get; }
public ObservableCollection<TimelineEasingViewModel> EasingViewModels { get; }
public double X
{
@ -93,7 +95,8 @@ public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineK
public ReactiveCommand<Unit, Unit> Copy { get; }
public ReactiveCommand<Unit, Unit> Paste { get; }
public ReactiveCommand<Unit, Unit> Delete { get; }
public ReactiveCommand<Easings.Functions, Unit> SelectEasingFunction { get; }
public bool IsSelected => _isSelected?.Value ?? false;
public TimeSpan Position => LayerPropertyKeyframe.Position;
public ILayerPropertyKeyframe Keyframe => LayerPropertyKeyframe;
@ -255,10 +258,10 @@ public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineK
EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions))
.Cast<Easings.Functions>()
.Select(e => new TimelineEasingViewModel(e, Keyframe)));
.Select(e => new TimelineEasingViewModel(e, Keyframe, SelectEasingFunction)));
}
public void SelectEasingFunction(Easings.Functions easingFunction)
private void ExecuteSelectEasingFunction(Easings.Functions easingFunction)
{
_profileEditorService.ExecuteCommand(new ChangeKeyframeEasing(Keyframe, easingFunction));
}

View File

@ -7,6 +7,7 @@ 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;
@ -17,14 +18,14 @@ public class SelectionAddToolViewModel : ToolViewModel
{
private readonly ObservableAsPropertyHelper<bool>? _isEnabled;
private readonly IProfileEditorService _profileEditorService;
private readonly IRgbService _rgbService;
private readonly IDeviceService _deviceService;
private Layer? _layer;
/// <inheritdoc />
public SelectionAddToolViewModel(IProfileEditorService profileEditorService, IRgbService rgbService)
public SelectionAddToolViewModel(IProfileEditorService profileEditorService, IDeviceService deviceService)
{
_profileEditorService = profileEditorService;
_rgbService = rgbService;
_deviceService = deviceService;
// Not disposed when deactivated but when really disposed
_isEnabled = profileEditorService.ProfileElement.Select(p => p is Layer).ToProperty(this, vm => vm.IsEnabled);
@ -45,9 +46,12 @@ public class SelectionAddToolViewModel : ToolViewModel
/// <inheritdoc />
public override MaterialIconKind Icon => MaterialIconKind.SelectionDrag;
/// <inheritdoc />
public override Hotkey? Hotkey { get; } = new(KeyboardKey.OemPlus, KeyboardModifierKey.Control);
/// <inheritdoc />
public override string ToolTip => "Add LEDs to the current layer";
public override string ToolTip => "Add LEDs to the current layer (Ctrl + +)";
public void AddLedsInRectangle(SKRect rect, bool expand, bool inverse)
{
@ -57,7 +61,7 @@ public class SelectionAddToolViewModel : ToolViewModel
if (inverse)
{
List<ArtemisLed> toRemove = _layer.Leds.Where(l => l.AbsoluteRectangle.IntersectsWith(rect)).ToList();
List<ArtemisLed> toAdd = _rgbService.EnabledDevices.SelectMany(d => d.Leds).Where(l => l.AbsoluteRectangle.IntersectsWith(rect)).Except(toRemove).ToList();
List<ArtemisLed> toAdd = _deviceService.EnabledDevices.SelectMany(d => d.Leds).Where(l => l.AbsoluteRectangle.IntersectsWith(rect)).Except(toRemove).ToList();
List<ArtemisLed> leds = _layer.Leds.Except(toRemove).ToList();
leds.AddRange(toAdd);
@ -65,7 +69,7 @@ public class SelectionAddToolViewModel : ToolViewModel
}
else
{
List<ArtemisLed> leds = _rgbService.EnabledDevices.SelectMany(d => d.Leds).Where(l => l.AbsoluteRectangle.IntersectsWith(rect)).ToList();
List<ArtemisLed> leds = _deviceService.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()));

View File

@ -4,8 +4,10 @@ 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;
@ -38,12 +40,15 @@ public class SelectionRemoveToolViewModel : ToolViewModel
/// <inheritdoc />
public override int Order => 3;
/// <inheritdoc />
public override Hotkey? Hotkey { get; } = new(KeyboardKey.OemMinus, KeyboardModifierKey.Control);
/// <inheritdoc />
public override MaterialIconKind Icon => MaterialIconKind.SelectOff;
/// <inheritdoc />
public override string ToolTip => "Remove LEDs from the current layer";
public override string ToolTip => "Remove LEDs from the current layer (Ctrl + -)";
public void RemoveLedsInRectangle(SKRect rect)
{

View File

@ -3,11 +3,13 @@ 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;
@ -95,12 +97,15 @@ public class TransformToolViewModel : ToolViewModel
/// <inheritdoc />
public override int Order => 3;
/// <inheritdoc />
public override Hotkey? Hotkey { get; } = new(KeyboardKey.T, KeyboardModifierKey.Control);
/// <inheritdoc />
public override MaterialIconKind Icon => MaterialIconKind.TransitConnectionVariant;
/// <inheritdoc />
public override string ToolTip => "Transform the shape of the current layer";
public override string ToolTip => "Transform the shape of the current layer (Ctrl+T)";
public Rect ShapeBounds
{

View File

@ -25,7 +25,7 @@ public class VisualEditorViewModel : ActivatableViewModelBase
private ObservableAsPropertyHelper<bool>? _suspendedEditing;
private ReadOnlyObservableCollection<IToolViewModel>? _tools;
public VisualEditorViewModel(IProfileEditorService profileEditorService, IRgbService rgbService, IProfileEditorVmFactory vmFactory)
public VisualEditorViewModel(IProfileEditorService profileEditorService, IDeviceService deviceService, IProfileEditorVmFactory vmFactory)
{
_vmFactory = vmFactory;
_visualizers = new SourceList<IVisualizerViewModel>();
@ -34,7 +34,7 @@ public class VisualEditorViewModel : ActivatableViewModelBase
.Bind(out ReadOnlyObservableCollection<IVisualizerViewModel> visualizers)
.Subscribe();
Devices = new ObservableCollection<ArtemisDevice>(rgbService.EnabledDevices.OrderBy(d => d.ZIndex));
Devices = new ObservableCollection<ArtemisDevice>(deviceService.EnabledDevices.OrderBy(d => d.ZIndex));
Visualizers = visualizers;
this.WhenActivated(d =>

View File

@ -8,19 +8,11 @@
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">
x:DataType="profileEditor:ProfileEditorViewModel"
Focusable="True">
<UserControl.Resources>
<converters:DoubleToGridLengthConverter x:Key="DoubleToGridLengthConverter" />
</UserControl.Resources>
<UserControl.KeyBindings>
<KeyBinding Command="{CompiledBinding History.Undo}" Gesture="Ctrl+Z" />
<KeyBinding Command="{CompiledBinding History.Redo}" Gesture="Ctrl+Y" />
<KeyBinding Command="{CompiledBinding ToggleSuspend}" Gesture="F5" />
<KeyBinding Command="{CompiledBinding ToggleAutoSuspend}" Gesture="Shift+F5" />
<KeyBinding Command="{CompiledBinding PropertiesViewModel.PlaybackViewModel.TogglePlay}" Gesture="Space" />
<KeyBinding Command="{CompiledBinding PropertiesViewModel.PlaybackViewModel.PlayFromStart}" Gesture="Shift+Space" />
<KeyBinding Command="{Binding TitleBarViewModel.MenuBarViewModel.CycleFocusMode}" Gesture="F" />
</UserControl.KeyBindings>
<UserControl.Styles>
<Style Selector="Border.suspended-editing">
<Setter Property="Margin" Value="-10" />

View File

@ -1,5 +1,3 @@
using Avalonia;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor;
@ -10,16 +8,4 @@ public partial class ProfileEditorView : ReactiveUserControl<ProfileEditorViewMo
{
InitializeComponent();
}
#region Overrides of Visual
/// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
Focus();
}
#endregion
}

View File

@ -28,6 +28,7 @@ public class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewModelParam
private readonly IProfileEditorService _profileEditorService;
private readonly IProfileService _profileService;
private readonly ISettingsService _settingsService;
private readonly IMainWindowService _mainWindowService;
private readonly SourceList<IToolViewModel> _tools;
private ObservableAsPropertyHelper<ProfileEditorHistory?>? _history;
private ProfileConfiguration? _profileConfiguration;
@ -44,11 +45,13 @@ public class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewModelParam
DisplayConditionScriptViewModel displayConditionScriptViewModel,
StatusBarViewModel statusBarViewModel,
IEnumerable<IToolViewModel> toolViewModels,
IMainWindowService mainWindowService)
IMainWindowService mainWindowService,
IInputService inputService)
{
_profileService = profileService;
_profileEditorService = profileEditorService;
_settingsService = settingsService;
_mainWindowService = mainWindowService;
_tools = new SourceList<IToolViewModel>();
_tools.AddRange(toolViewModels);
@ -72,11 +75,13 @@ public class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewModelParam
_history = profileEditorService.History.ToProperty(this, vm => vm.History).DisposeWith(d);
_suspendedEditing = profileEditorService.SuspendedEditing.ToProperty(this, vm => vm.SuspendedEditing).DisposeWith(d);
inputService.KeyboardKeyDown += InputServiceOnKeyboardKeyDown;
mainWindowService.MainWindowFocused += MainWindowServiceOnMainWindowFocused;
mainWindowService.MainWindowUnfocused += MainWindowServiceOnMainWindowUnfocused;
Disposable.Create(() =>
{
inputService.KeyboardKeyDown -= InputServiceOnKeyboardKeyDown;
mainWindowService.MainWindowFocused -= MainWindowServiceOnMainWindowFocused;
mainWindowService.MainWindowUnfocused -= MainWindowServiceOnMainWindowUnfocused;
foreach (IToolViewModel toolViewModel in _tools.Items)
@ -139,6 +144,33 @@ public class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewModelParam
});
}
private void InputServiceOnKeyboardKeyDown(object? sender, ArtemisKeyboardKeyEventArgs e)
{
if (!Shared.UI.KeyBindingsEnabled || !_mainWindowService.IsMainWindowFocused)
return;
if (e.Modifiers == KeyboardModifierKey.Control && e.Key == KeyboardKey.Z)
History?.Undo.Execute().Subscribe();
else if (e.Modifiers == KeyboardModifierKey.Control && e.Key == KeyboardKey.Y)
History?.Redo.Execute().Subscribe();
else if (e.Modifiers == KeyboardModifierKey.None && e.Key == KeyboardKey.F5)
ToggleSuspend.Execute().Subscribe();
else if (e.Modifiers == KeyboardModifierKey.Shift && e.Key == KeyboardKey.F5)
ToggleAutoSuspend.Execute().Subscribe();
else if (e.Modifiers == KeyboardModifierKey.None && e.Key == KeyboardKey.Space)
PropertiesViewModel?.PlaybackViewModel.TogglePlay.Execute().Subscribe();
else if (e.Modifiers == KeyboardModifierKey.Shift && e.Key == KeyboardKey.Space)
PropertiesViewModel?.PlaybackViewModel.PlayFromStart.Execute().Subscribe();
else if (e.Modifiers == KeyboardModifierKey.None && e.Key == KeyboardKey.F)
(TitleBarViewModel as ProfileEditorTitleBarViewModel)?.MenuBarViewModel.CycleFocusMode.Execute().Subscribe();
else
{
IToolViewModel? tool = Tools.FirstOrDefault(t => t.Hotkey != null && t.Hotkey.MatchesEventArgs(e));
if (tool != null)
tool.IsSelected = true;
}
}
private void MainWindowServiceOnMainWindowFocused(object? sender, EventArgs e)
{
if (_settingsService.GetSetting("ProfileEditor.AutoSuspend", true).Value)

View File

@ -19,15 +19,15 @@ namespace Artemis.UI.Screens.Settings;
public class DevicesTabViewModel : RoutableScreen
{
private readonly IDeviceVmFactory _deviceVmFactory;
private readonly IRgbService _rgbService;
private readonly IDeviceService _deviceService;
private readonly IWindowService _windowService;
private bool _confirmedDisable;
public DevicesTabViewModel(IRgbService rgbService, IWindowService windowService, IDeviceVmFactory deviceVmFactory)
public DevicesTabViewModel(IDeviceService deviceService, IWindowService windowService, IDeviceVmFactory deviceVmFactory)
{
DisplayName = "Devices";
_rgbService = rgbService;
_deviceService = deviceService;
_windowService = windowService;
_deviceVmFactory = deviceVmFactory;
@ -36,10 +36,10 @@ public class DevicesTabViewModel : RoutableScreen
{
GetDevices();
Observable.FromEventPattern<DeviceEventArgs>(x => rgbService.DeviceAdded += x, x => rgbService.DeviceAdded -= x)
Observable.FromEventPattern<DeviceEventArgs>(x => _deviceService.DeviceAdded += x, x => _deviceService.DeviceAdded -= x)
.Subscribe(d => AddDevice(d.EventArgs.Device))
.DisposeWith(disposables);
Observable.FromEventPattern<DeviceEventArgs>(x => rgbService.DeviceRemoved += x, x => rgbService.DeviceRemoved -= x)
Observable.FromEventPattern<DeviceEventArgs>(x => _deviceService.DeviceRemoved += x, x => _deviceService.DeviceRemoved -= x)
.Subscribe(d => RemoveDevice(d.EventArgs.Device))
.DisposeWith(disposables);
});
@ -66,7 +66,7 @@ public class DevicesTabViewModel : RoutableScreen
private void GetDevices()
{
Devices.Clear();
Dispatcher.UIThread.InvokeAsync(() => { Devices.AddRange(_rgbService.Devices.Select(d => _deviceVmFactory.DeviceSettingsViewModel(d, this))); }, DispatcherPriority.Background);
Dispatcher.UIThread.InvokeAsync(() => { Devices.AddRange(_deviceService.Devices.Select(d => _deviceVmFactory.DeviceSettingsViewModel(d, this))); }, DispatcherPriority.Background);
}
private void AddDevice(ArtemisDevice device)
@ -81,7 +81,7 @@ public class DevicesTabViewModel : RoutableScreen
private void RemoveDevice(ArtemisDevice device)
{
// If the device was only disabled don't remove it
if (_rgbService.Devices.Contains(device))
if (_deviceService.Devices.Contains(device))
return;
DeviceSettingsViewModel? viewModel = Devices.FirstOrDefault(i => i.Device == device);

View File

@ -21,9 +21,9 @@ public class StartupWizardViewModel : DialogViewModelBase<bool>
{
private readonly IAutoRunProvider? _autoRunProvider;
private readonly IProtocolProvider? _protocolProvider;
private readonly IRgbService _rgbService;
private readonly ISettingsService _settingsService;
private readonly IWindowService _windowService;
private readonly IDeviceService _deviceService;
private int _currentStep;
private bool _showContinue;
private bool _showFinish;
@ -31,14 +31,14 @@ public class StartupWizardViewModel : DialogViewModelBase<bool>
public StartupWizardViewModel(IContainer container,
ISettingsService settingsService,
IRgbService rgbService,
IPluginManagementService pluginManagementService,
IWindowService windowService,
IDeviceService deviceService,
ISettingsVmFactory settingsVmFactory)
{
_settingsService = settingsService;
_rgbService = rgbService;
_windowService = windowService;
_deviceService = deviceService;
_autoRunProvider = container.Resolve<IAutoRunProvider>(IfUnresolved.ReturnDefault);
_protocolProvider = container.Resolve<IProtocolProvider>(IfUnresolved.ReturnDefault);
@ -159,7 +159,7 @@ public class StartupWizardViewModel : DialogViewModelBase<bool>
private void ExecuteSelectLayout(string layout)
{
// TODO: Implement the layout
_rgbService.AutoArrangeDevices();
_deviceService.AutoArrangeDevices();
ExecuteContinue();
}

View File

@ -15,16 +15,16 @@ namespace Artemis.UI.Screens.SurfaceEditor;
public class ListDeviceViewModel : ViewModelBase
{
private static readonly Random Random = new();
private readonly IRgbService _rgbService;
private readonly IWindowService _windowService;
private readonly IDeviceService _deviceService;
private SKColor _color;
private bool _isSelected;
public ListDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel, IWindowService windowService, IRgbService rgbService)
public ListDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel, IWindowService windowService, IDeviceService deviceService)
{
_windowService = windowService;
_rgbService = rgbService;
_deviceService = deviceService;
Device = device;
SurfaceEditorViewModel = surfaceEditorViewModel;
@ -62,6 +62,6 @@ public class ListDeviceViewModel : ViewModelBase
.ShowAsync();
if (viewModel.MadeChanges)
_rgbService.SaveDevice(Device);
_deviceService.SaveDevice(Device);
}
}

View File

@ -17,16 +17,16 @@ namespace Artemis.UI.Screens.SurfaceEditor;
public class SurfaceDeviceViewModel : ActivatableViewModelBase
{
private readonly IRgbService _rgbService;
private readonly IDeviceService _deviceService;
private readonly ISettingsService _settingsService;
private readonly IWindowService _windowService;
private double _dragOffsetX;
private double _dragOffsetY;
private bool _isSelected;
public SurfaceDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel, IRgbService rgbService, ISettingsService settingsService, IWindowService windowService)
public SurfaceDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel, IDeviceService deviceService, ISettingsService settingsService, IWindowService windowService)
{
_rgbService = rgbService;
_deviceService = deviceService;
_settingsService = settingsService;
_windowService = windowService;
@ -100,7 +100,7 @@ public class SurfaceDeviceViewModel : ActivatableViewModelBase
IEnumerable<SKRect> own = Device.Leds
.Select(l => SKRect.Create(l.Rectangle.Left + x, l.Rectangle.Top + y, l.Rectangle.Width, l.Rectangle.Height));
IEnumerable<SKRect> others = _rgbService.EnabledDevices
IEnumerable<SKRect> others = _deviceService.EnabledDevices
.Where(d => d != Device && d.IsEnabled)
.SelectMany(d => d.Leds)
.Select(l => SKRect.Create(l.Rectangle.Left + l.Device.X, l.Rectangle.Top + l.Device.Y, l.Rectangle.Width, l.Rectangle.Height));
@ -120,6 +120,6 @@ public class SurfaceDeviceViewModel : ActivatableViewModelBase
.ShowAsync();
if (viewModel.MadeChanges)
_rgbService.SaveDevice(Device);
_deviceService.SaveDevice(Device);
}
}

View File

@ -99,7 +99,7 @@
</ItemsControl>
<shared:SelectionRectangle Name="SelectionRectangle"
InputElement="{Binding #ZoomBorder}"
InputElement="{Binding #ContainerZoomBorder}"
SelectionUpdated="SelectionRectangle_OnSelectionUpdated"
BorderBrush="{DynamicResource SystemAccentColor}"
BorderRadius="8"

View File

@ -21,8 +21,8 @@ namespace Artemis.UI.Screens.SurfaceEditor;
public class SurfaceEditorViewModel : RoutableScreen, IMainScreenViewModel
{
private readonly IDeviceService _deviceService;
private readonly IRenderService _renderService;
private readonly IDeviceVmFactory _deviceVmFactory;
private readonly IRgbService _rgbService;
private readonly ISettingsService _settingsService;
private readonly ISurfaceVmFactory _surfaceVmFactory;
private readonly IWindowService _windowService;
@ -33,23 +33,23 @@ public class SurfaceEditorViewModel : RoutableScreen, IMainScreenViewModel
private bool _saving;
public SurfaceEditorViewModel(ICoreService coreService,
IRgbService rgbService,
ISurfaceVmFactory surfaceVmFactory,
ISettingsService settingsService,
IDeviceVmFactory deviceVmFactory,
IWindowService windowService,
IDeviceService deviceService)
IDeviceService deviceService,
IRenderService renderService)
{
_rgbService = rgbService;
_surfaceVmFactory = surfaceVmFactory;
_settingsService = settingsService;
_deviceVmFactory = deviceVmFactory;
_windowService = windowService;
_deviceService = deviceService;
_renderService = renderService;
DisplayName = "Surface Editor";
SurfaceDeviceViewModels = new ObservableCollection<SurfaceDeviceViewModel>(rgbService.EnabledDevices.OrderBy(d => d.ZIndex).Select(d => surfaceVmFactory.SurfaceDeviceViewModel(d, this)));
ListDeviceViewModels = new ObservableCollection<ListDeviceViewModel>(rgbService.EnabledDevices.OrderBy(d => d.ZIndex).Select(d => surfaceVmFactory.ListDeviceViewModel(d, this)));
SurfaceDeviceViewModels = new ObservableCollection<SurfaceDeviceViewModel>(deviceService.EnabledDevices.OrderBy(d => d.ZIndex).Select(d => surfaceVmFactory.SurfaceDeviceViewModel(d, this)));
ListDeviceViewModels = new ObservableCollection<ListDeviceViewModel>(deviceService.EnabledDevices.OrderBy(d => d.ZIndex).Select(d => surfaceVmFactory.ListDeviceViewModel(d, this)));
AutoArrange = ReactiveCommand.CreateFromTask(ExecuteAutoArrange);
IdentifyDevice = ReactiveCommand.Create<ArtemisDevice>(ExecuteIdentifyDevice);
@ -61,14 +61,14 @@ public class SurfaceEditorViewModel : RoutableScreen, IMainScreenViewModel
this.WhenActivated(d =>
{
coreService.FrameRendering += CoreServiceOnFrameRendering;
_rgbService.DeviceAdded += RgbServiceOnDeviceAdded;
_rgbService.DeviceRemoved += RgbServiceOnDeviceRemoved;
_renderService.FrameRendering += RenderServiceOnFrameRendering;
_deviceService.DeviceAdded += DeviceServiceOnDeviceAdded;
_deviceService.DeviceRemoved += DeviceServiceOnDeviceRemoved;
Disposable.Create(() =>
{
coreService.FrameRendering -= CoreServiceOnFrameRendering;
_rgbService.DeviceAdded -= RgbServiceOnDeviceAdded;
_rgbService.DeviceRemoved -= RgbServiceOnDeviceRemoved;
_renderService.FrameRendering -= RenderServiceOnFrameRendering;
_deviceService.DeviceAdded -= DeviceServiceOnDeviceAdded;
_deviceService.DeviceRemoved -= DeviceServiceOnDeviceRemoved;
}).DisposeWith(d);
});
}
@ -135,7 +135,7 @@ public class SurfaceEditorViewModel : RoutableScreen, IMainScreenViewModel
try
{
_saving = true;
_rgbService.SaveDevices();
_deviceService.SaveDevices();
}
catch (Exception e)
{
@ -166,7 +166,7 @@ public class SurfaceEditorViewModel : RoutableScreen, IMainScreenViewModel
surfaceDeviceViewModel.UpdateMouseDrag(mousePosition, round, ignoreOverlap);
}
private void RgbServiceOnDeviceAdded(object? sender, DeviceEventArgs e)
private void DeviceServiceOnDeviceAdded(object? sender, DeviceEventArgs e)
{
if (!e.Device.IsEnabled)
return;
@ -177,7 +177,7 @@ public class SurfaceEditorViewModel : RoutableScreen, IMainScreenViewModel
ListDeviceViewModels.Sort(l => l.Device.ZIndex * -1);
}
private void RgbServiceOnDeviceRemoved(object? sender, DeviceEventArgs e)
private void DeviceServiceOnDeviceRemoved(object? sender, DeviceEventArgs e)
{
SurfaceDeviceViewModel? surfaceVm = SurfaceDeviceViewModels.FirstOrDefault(vm => vm.Device == e.Device);
ListDeviceViewModel? listVm = ListDeviceViewModels.FirstOrDefault(vm => vm.Device == e.Device);
@ -193,10 +193,10 @@ public class SurfaceEditorViewModel : RoutableScreen, IMainScreenViewModel
if (!confirmed)
return;
_rgbService.AutoArrangeDevices();
_deviceService.AutoArrangeDevices();
}
private void CoreServiceOnFrameRendering(object? sender, FrameRenderingEventArgs e)
private void RenderServiceOnFrameRendering(object? sender, FrameRenderingEventArgs e)
{
// Animate the overlay because I'm vain
if (ColorDevices && _overlayOpacity < 1)
@ -255,7 +255,7 @@ public class SurfaceEditorViewModel : RoutableScreen, IMainScreenViewModel
ListDeviceViewModels.Sort(l => l.Device.ZIndex * -1);
_rgbService.SaveDevices();
_deviceService.SaveDevices();
}
private void ExecuteBringForward(ArtemisDevice device)
@ -273,7 +273,7 @@ public class SurfaceEditorViewModel : RoutableScreen, IMainScreenViewModel
ListDeviceViewModels.Sort(l => l.Device.ZIndex * -1);
_rgbService.SaveDevices();
_deviceService.SaveDevices();
}
private void ExecuteSendToBack(ArtemisDevice device)
@ -288,7 +288,7 @@ public class SurfaceEditorViewModel : RoutableScreen, IMainScreenViewModel
ListDeviceViewModels.Sort(l => l.Device.ZIndex * -1);
_rgbService.SaveDevices();
_deviceService.SaveDevices();
}
private void ExecuteSendBackward(ArtemisDevice device)
@ -305,7 +305,7 @@ public class SurfaceEditorViewModel : RoutableScreen, IMainScreenViewModel
ListDeviceViewModels.Sort(l => l.Device.ZIndex * -1);
_rgbService.SaveDevices();
_deviceService.SaveDevices();
}
#endregion

View File

@ -64,7 +64,7 @@ public class NodeScriptWindowViewModel : NodeScriptWindowViewModelBase
this.WhenActivated(d =>
{
_keyBindingsEnabled = Shared.UI.KeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d);
_keyBindingsEnabled = Shared.UI.CurrentKeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d);
Timer updateTimer = new(TimeSpan.FromMilliseconds(25.0 / 1000));
Timer saveTimer = new(TimeSpan.FromMinutes(2));

View File

@ -16,12 +16,12 @@ public class ProfilePreviewViewModel : ActivatableViewModelBase
private readonly IWindowService _windowService;
private ProfileConfiguration? _profileConfiguration;
public ProfilePreviewViewModel(IProfileService profileService, IRgbService rgbService, IWindowService windowService)
public ProfilePreviewViewModel(IProfileService profileService, IDeviceService deviceService, IWindowService windowService)
{
_profileService = profileService;
_windowService = windowService;
Devices = new ObservableCollection<ArtemisDevice>(rgbService.EnabledDevices.OrderBy(d => d.ZIndex));
Devices = new ObservableCollection<ArtemisDevice>(deviceService.EnabledDevices.OrderBy(d => d.ZIndex));
this.WhenAnyValue(vm => vm.ProfileConfiguration).Subscribe(_ => Update());
this.WhenActivated(d => Disposable.Create(() => PreviewProfile(null)).DisposeWith(d));

View File

@ -17,18 +17,18 @@ namespace Artemis.UI.Services;
public class DeviceLayoutService : IDeviceLayoutService
{
private readonly List<ArtemisDevice> _ignoredDevices;
private readonly IDeviceService _deviceService;
private readonly IMainWindowService _mainWindowService;
private readonly IRgbService _rgbService;
private readonly IWindowService _windowService;
public DeviceLayoutService(IRgbService rgbService, IMainWindowService mainWindowService, IWindowService windowService)
public DeviceLayoutService(IDeviceService deviceService, IMainWindowService mainWindowService, IWindowService windowService)
{
_rgbService = rgbService;
_deviceService = deviceService;
_mainWindowService = mainWindowService;
_windowService = windowService;
_ignoredDevices = new List<ArtemisDevice>();
rgbService.DeviceAdded += RgbServiceOnDeviceAdded;
deviceService.DeviceAdded += RgbServiceOnDeviceAdded;
mainWindowService.MainWindowOpened += WindowServiceOnMainWindowOpened;
}
@ -60,8 +60,8 @@ public class DeviceLayoutService : IDeviceLayoutService
}
await Task.Delay(400);
_rgbService.SaveDevice(artemisDevice);
_rgbService.ApplyBestDeviceLayout(artemisDevice);
_deviceService.SaveDevice(artemisDevice);
_deviceService.ApplyDeviceLayout(artemisDevice, artemisDevice.GetBestDeviceLayout());
}
private bool DeviceNeedsLayout(ArtemisDevice d)
@ -73,7 +73,7 @@ public class DeviceLayoutService : IDeviceLayoutService
private async void WindowServiceOnMainWindowOpened(object? sender, EventArgs e)
{
List<ArtemisDevice> devices = _rgbService.Devices.Where(device => DeviceNeedsLayout(device) && !_ignoredDevices.Contains(device)).ToList();
List<ArtemisDevice> devices = _deviceService.Devices.Where(device => DeviceNeedsLayout(device) && !_ignoredDevices.Contains(device)).ToList();
await Dispatcher.UIThread.InvokeAsync(async () =>
{
foreach (ArtemisDevice artemisDevice in devices)