1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-31 17:53:32 +00:00

Merge branch 'development'

This commit is contained in:
Robert 2021-03-16 20:26:24 +01:00
commit eeaa374d40
26 changed files with 499 additions and 365 deletions

View File

@ -20,14 +20,14 @@ namespace Artemis.Core.JsonConverters
{ {
JValue? jsonValue = serializer.Deserialize<JValue>(reader); JValue? jsonValue = serializer.Deserialize<JValue>(reader);
if (jsonValue == null) if (jsonValue == null)
throw new FormatException(); throw new JsonReaderException("Failed to deserialize forgiving int value");
if (jsonValue.Type == JTokenType.Float) if (jsonValue.Type == JTokenType.Float)
return (int) Math.Round(jsonValue.Value<double>()); return (int) Math.Round(jsonValue.Value<double>());
if (jsonValue.Type == JTokenType.Integer) if (jsonValue.Type == JTokenType.Integer)
return jsonValue.Value<int>(); return jsonValue.Value<int>();
throw new FormatException(); throw new JsonReaderException("Failed to deserialize forgiving int value");
} }
} }
} }

View File

@ -94,19 +94,14 @@ namespace Artemis.Core
private bool EvaluateWithOperator(IEnumerable<DataModelConditionPart> targets) private bool EvaluateWithOperator(IEnumerable<DataModelConditionPart> targets)
{ {
switch (BooleanOperator) return BooleanOperator switch
{ {
case BooleanOperator.And: BooleanOperator.And => targets.All(c => c.Evaluate()),
return targets.All(c => c.Evaluate()); BooleanOperator.Or => targets.Any(c => c.Evaluate()),
case BooleanOperator.Or: BooleanOperator.AndNot => targets.All(c => !c.Evaluate()),
return targets.Any(c => c.Evaluate()); BooleanOperator.OrNot => targets.Any(c => !c.Evaluate()),
case BooleanOperator.AndNot: _ => throw new ArgumentOutOfRangeException()
return targets.All(c => !c.Evaluate()); };
case BooleanOperator.OrNot:
return targets.Any(c => !c.Evaluate());
default:
throw new ArgumentOutOfRangeException();
}
} }
#region IDisposable #region IDisposable

View File

@ -64,5 +64,15 @@ namespace Artemis.Core
DataBinding = new DataBinding<TLayerProperty, TProperty>(LayerProperty, dataBinding); DataBinding = new DataBinding<TLayerProperty, TProperty>(LayerProperty, dataBinding);
return DataBinding; return DataBinding;
} }
/// <inheritdoc />
public void ClearDataBinding()
{
if (DataBinding == null)
return;
// The related entity is left behind, just in case the data binding is added back later
LayerProperty.DisableDataBinding(DataBinding);
}
} }
} }

View File

@ -20,5 +20,10 @@
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
IDataBinding? CreateDataBinding(); IDataBinding? CreateDataBinding();
/// <summary>
/// If present, removes the current data binding
/// </summary>
void ClearDataBinding();
} }
} }

View File

@ -420,6 +420,14 @@ namespace Artemis.Core
DataBindingRegistration<T, TProperty> registration = new(this, converter, getter, setter, displayName); DataBindingRegistration<T, TProperty> registration = new(this, converter, getter, setter, displayName);
_dataBindingRegistrations.Add(registration); _dataBindingRegistrations.Add(registration);
// If not yet initialized, load the data binding related to the registration if available
if (_isInitialized)
{
IDataBinding? dataBinding = registration.CreateDataBinding();
if (dataBinding != null)
_dataBindings.Add(dataBinding);
}
OnDataBindingPropertyRegistered(); OnDataBindingPropertyRegistered();
return registration; return registration;
} }
@ -432,7 +440,10 @@ namespace Artemis.Core
if (_disposed) if (_disposed)
throw new ObjectDisposedException("LayerProperty"); throw new ObjectDisposedException("LayerProperty");
foreach (IDataBindingRegistration dataBindingRegistration in _dataBindingRegistrations)
dataBindingRegistration.ClearDataBinding();
_dataBindingRegistrations.Clear(); _dataBindingRegistrations.Clear();
OnDataBindingPropertiesCleared(); OnDataBindingPropertiesCleared();
} }

View File

@ -10,12 +10,15 @@ namespace Artemis.Core
/// </summary> /// </summary>
public sealed class SKTexture : PixelTexture<byte>, IDisposable public sealed class SKTexture : PixelTexture<byte>, IDisposable
{ {
private bool _disposed;
#region Constructors #region Constructors
internal SKTexture(SKBitmap bitmap) internal SKTexture(int width, int height, float renderScale)
: base(bitmap.Width, bitmap.Height, 4, new AverageByteSampler()) : base(width, height, 4, new AverageByteSampler())
{ {
Bitmap = bitmap; Bitmap = new SKBitmap(new SKImageInfo(width, height, SKColorType.Rgb888x));
RenderScale = renderScale;
} }
#endregion #endregion
@ -40,11 +43,31 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets the color data in RGB format /// Gets the color data in RGB format
/// </summary> /// </summary>
protected override ReadOnlySpan<byte> Data => Bitmap.GetPixelSpan(); protected override ReadOnlySpan<byte> Data => _disposed ? new ReadOnlySpan<byte>() : Bitmap.GetPixelSpan();
/// <summary>
/// Gets the render scale of the texture
/// </summary>
public float RenderScale { get; }
/// <summary>
/// Gets a boolean indicating whether <see cref="Invalidate" /> has been called on this texture, indicating it should
/// be replaced
/// </summary>
public bool IsInvalid { get; private set; }
/// <summary>
/// Invalidates the texture
/// </summary>
public void Invalidate()
{
IsInvalid = true;
}
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {
_disposed = true;
Bitmap.Dispose(); Bitmap.Dispose();
} }

View File

@ -29,14 +29,11 @@ namespace Artemis.Core.Services
private readonly PluginSetting<LogEventLevel> _loggingLevel; private readonly PluginSetting<LogEventLevel> _loggingLevel;
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private readonly IProfileService _profileService; private readonly IProfileService _profileService;
private readonly PluginSetting<double> _renderScale;
private readonly IRgbService _rgbService; private readonly IRgbService _rgbService;
private readonly List<Exception> _updateExceptions = new(); private readonly List<Exception> _updateExceptions = new();
private List<BaseDataModelExpansion> _dataModelExpansions = new(); private List<BaseDataModelExpansion> _dataModelExpansions = new();
private DateTime _lastExceptionLog; private DateTime _lastExceptionLog;
private List<Module> _modules = new(); private List<Module> _modules = new();
private SKBitmap? _bitmap;
private readonly object _bitmapLock = new();
// ReSharper disable UnusedParameter.Local // ReSharper disable UnusedParameter.Local
public CoreService(IKernel kernel, public CoreService(IKernel kernel,
@ -57,17 +54,13 @@ namespace Artemis.Core.Services
_rgbService = rgbService; _rgbService = rgbService;
_profileService = profileService; _profileService = profileService;
_loggingLevel = settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Debug); _loggingLevel = settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Debug);
_renderScale = settingsService.GetSetting("Core.RenderScale", 0.5);
_frameStopWatch = new Stopwatch(); _frameStopWatch = new Stopwatch();
StartupArguments = new List<string>(); StartupArguments = new List<string>();
UpdatePluginCache(); UpdatePluginCache();
_rgbService.Surface.Updating += SurfaceOnUpdating; _rgbService.Surface.Updating += SurfaceOnUpdating;
_rgbService.Surface.Updated += SurfaceOnUpdated;
_rgbService.Surface.SurfaceLayoutChanged += SurfaceOnSurfaceLayoutChanged;
_loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel(); _loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel();
_renderScale.SettingChanged += RenderScaleSettingChanged;
_pluginManagementService.PluginFeatureEnabled += (sender, args) => UpdatePluginCache(); _pluginManagementService.PluginFeatureEnabled += (sender, args) => UpdatePluginCache();
_pluginManagementService.PluginFeatureDisabled += (sender, args) => UpdatePluginCache(); _pluginManagementService.PluginFeatureDisabled += (sender, args) => UpdatePluginCache();
@ -75,6 +68,133 @@ namespace Artemis.Core.Services
// ReSharper restore UnusedParameter.Local // ReSharper restore UnusedParameter.Local
protected virtual void OnFrameRendering(FrameRenderingEventArgs e)
{
FrameRendering?.Invoke(this, e);
}
protected virtual void OnFrameRendered(FrameRenderedEventArgs e)
{
FrameRendered?.Invoke(this, e);
}
private void UpdatePluginCache()
{
_modules = _pluginManagementService.GetFeaturesOfType<Module>().Where(p => p.IsEnabled).ToList();
_dataModelExpansions = _pluginManagementService.GetFeaturesOfType<BaseDataModelExpansion>().Where(p => p.IsEnabled).ToList();
}
private void ApplyLoggingLevel()
{
string? argument = StartupArguments.FirstOrDefault(a => a.StartsWith("--logging"));
if (argument != null)
{
// Parse the provided log level
string[] parts = argument.Split('=');
if (parts.Length == 2 && Enum.TryParse(typeof(LogEventLevel), parts[1], true, out object? logLevelArgument))
{
_logger.Information("Setting logging level to {loggingLevel} from startup argument", (LogEventLevel) logLevelArgument!);
LoggerProvider.LoggingLevelSwitch.MinimumLevel = (LogEventLevel) logLevelArgument;
}
else
{
_logger.Warning("Failed to set log level from startup argument {argument}", argument);
_logger.Information("Setting logging level to {loggingLevel}", _loggingLevel.Value);
LoggerProvider.LoggingLevelSwitch.MinimumLevel = _loggingLevel.Value;
}
}
else
{
_logger.Information("Setting logging level to {loggingLevel}", _loggingLevel.Value);
LoggerProvider.LoggingLevelSwitch.MinimumLevel = _loggingLevel.Value;
}
}
private void SurfaceOnUpdating(UpdatingEventArgs args)
{
if (_rgbService.IsRenderPaused)
return;
try
{
_frameStopWatch.Restart();
lock (_dataModelExpansions)
{
// Update all active modules, check Enabled status because it may go false before before the _dataModelExpansions list is updated
foreach (BaseDataModelExpansion dataModelExpansion in _dataModelExpansions.Where(e => e.IsEnabled))
dataModelExpansion.InternalUpdate(args.DeltaTime);
}
List<Module> modules;
lock (_modules)
{
modules = _modules.Where(m => m.IsActivated || m.InternalExpandsMainDataModel)
.OrderBy(m => m.PriorityCategory)
.ThenByDescending(m => m.Priority)
.ToList();
}
// Update all active modules
foreach (Module module in modules)
module.InternalUpdate(args.DeltaTime);
// Render all active modules
SKTexture texture =_rgbService.OpenRender();
using (SKCanvas canvas = new(texture.Bitmap))
{
canvas.Scale(texture.RenderScale);
canvas.Clear(new SKColor(0, 0, 0));
// While non-activated modules may be updated above if they expand the main data model, they may never render
if (!ModuleRenderingDisabled)
{
foreach (Module module in modules.Where(m => m.IsActivated))
module.InternalRender(args.DeltaTime, canvas, texture.Bitmap.Info);
}
OnFrameRendering(new FrameRenderingEventArgs(canvas, args.DeltaTime, _rgbService.Surface));
}
OnFrameRendered(new FrameRenderedEventArgs(texture, _rgbService.Surface));
}
catch (Exception e)
{
_updateExceptions.Add(e);
}
finally
{
_frameStopWatch.Stop();
FrameTime = _frameStopWatch.Elapsed;
_rgbService.CloseRender();
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();
}
private void OnInitialized()
{
IsInitialized = true;
Initialized?.Invoke(this, EventArgs.Empty);
}
public TimeSpan FrameTime { get; private set; } public TimeSpan FrameTime { get; private set; }
public bool ModuleRenderingDisabled { get; set; } public bool ModuleRenderingDisabled { get; set; }
public List<string> StartupArguments { get; set; } public List<string> StartupArguments { get; set; }
@ -123,16 +243,6 @@ namespace Artemis.Core.Services
OnInitialized(); OnInitialized();
} }
protected virtual void OnFrameRendering(FrameRenderingEventArgs e)
{
FrameRendering?.Invoke(this, e);
}
protected virtual void OnFrameRendered(FrameRenderedEventArgs e)
{
FrameRendered?.Invoke(this, e);
}
public void PlayIntroAnimation() public void PlayIntroAnimation()
{ {
IntroAnimation intro = new(_logger, _profileService, _rgbService.EnabledDevices); IntroAnimation intro = new(_logger, _profileService, _rgbService.EnabledDevices);
@ -156,155 +266,8 @@ namespace Artemis.Core.Services
}); });
} }
private void UpdatePluginCache()
{
_modules = _pluginManagementService.GetFeaturesOfType<Module>().Where(p => p.IsEnabled).ToList();
_dataModelExpansions = _pluginManagementService.GetFeaturesOfType<BaseDataModelExpansion>().Where(p => p.IsEnabled).ToList();
}
private void ApplyLoggingLevel()
{
string? argument = StartupArguments.FirstOrDefault(a => a.StartsWith("--logging"));
if (argument != null)
{
// Parse the provided log level
string[] parts = argument.Split('=');
if (parts.Length == 2 && Enum.TryParse(typeof(LogEventLevel), parts[1], true, out object? logLevelArgument))
{
_logger.Information("Setting logging level to {loggingLevel} from startup argument", (LogEventLevel)logLevelArgument!);
LoggerProvider.LoggingLevelSwitch.MinimumLevel = (LogEventLevel)logLevelArgument;
}
else
{
_logger.Warning("Failed to set log level from startup argument {argument}", argument);
_logger.Information("Setting logging level to {loggingLevel}", _loggingLevel.Value);
LoggerProvider.LoggingLevelSwitch.MinimumLevel = _loggingLevel.Value;
}
}
else
{
_logger.Information("Setting logging level to {loggingLevel}", _loggingLevel.Value);
LoggerProvider.LoggingLevelSwitch.MinimumLevel = _loggingLevel.Value;
}
}
private void SurfaceOnUpdating(UpdatingEventArgs args)
{
if (_rgbService.IsRenderPaused)
return;
try
{
_frameStopWatch.Restart();
lock (_dataModelExpansions)
{
// Update all active modules, check Enabled status because it may go false before before the _dataModelExpansions list is updated
foreach (BaseDataModelExpansion dataModelExpansion in _dataModelExpansions.Where(e => e.IsEnabled))
dataModelExpansion.InternalUpdate(args.DeltaTime);
}
List<Module> modules;
lock (_modules)
{
modules = _modules.Where(m => m.IsActivated || m.InternalExpandsMainDataModel)
.OrderBy(m => m.PriorityCategory)
.ThenByDescending(m => m.Priority)
.ToList();
}
// Update all active modules
foreach (Module module in modules)
module.InternalUpdate(args.DeltaTime);
lock (_bitmapLock)
{
if (_bitmap == null)
{
_bitmap = CreateBitmap();
_rgbService.UpdateTexture(_bitmap);
}
// Render all active modules
using SKCanvas canvas = new(_bitmap);
canvas.Scale((float)_renderScale.Value);
canvas.Clear(new SKColor(0, 0, 0));
if (!ModuleRenderingDisabled)
// While non-activated modules may be updated above if they expand the main data model, they may never render
foreach (Module module in modules.Where(m => m.IsActivated))
module.InternalRender(args.DeltaTime, canvas, _bitmap.Info);
OnFrameRendering(new FrameRenderingEventArgs(canvas, args.DeltaTime, _rgbService.Surface));
}
}
catch (Exception e)
{
_updateExceptions.Add(e);
}
finally
{
_frameStopWatch.Stop();
FrameTime = _frameStopWatch.Elapsed;
LogUpdateExceptions();
}
}
private SKBitmap CreateBitmap()
{
float width = MathF.Min(_rgbService.Surface.Boundary.Size.Width * (float)_renderScale.Value, 4096);
float height = MathF.Min(_rgbService.Surface.Boundary.Size.Height * (float)_renderScale.Value, 4096);
return new SKBitmap(new SKImageInfo(width.RoundToInt(), height.RoundToInt(), SKColorType.Rgb888x));
}
private void InvalidateBitmap()
{
lock (_bitmapLock)
{
_bitmap = null;
}
}
private void SurfaceOnSurfaceLayoutChanged(SurfaceLayoutChangedEventArgs args) => InvalidateBitmap();
private void RenderScaleSettingChanged(object? sender, EventArgs e) => InvalidateBitmap();
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 SurfaceOnUpdated(UpdatedEventArgs args)
{
if (_rgbService.IsRenderPaused)
return;
OnFrameRendered(new FrameRenderedEventArgs(_rgbService.Texture!, _rgbService.Surface));
}
#region Events
public event EventHandler? Initialized; public event EventHandler? Initialized;
public event EventHandler<FrameRenderingEventArgs>? FrameRendering; public event EventHandler<FrameRenderingEventArgs>? FrameRendering;
public event EventHandler<FrameRenderedEventArgs>? FrameRendered; public event EventHandler<FrameRenderedEventArgs>? FrameRendered;
private void OnInitialized()
{
IsInitialized = true;
Initialized?.Invoke(this, EventArgs.Empty);
}
#endregion
} }
} }

View File

@ -54,7 +54,7 @@ namespace Artemis.Core.Services
event EventHandler<FrameRenderingEventArgs> FrameRendering; event EventHandler<FrameRenderingEventArgs> FrameRendering;
/// <summary> /// <summary>
/// Occurs whenever a frame is finished rendering and processed by RGB.NET /// Occurs whenever a frame is finished rendering and the render pipeline is closed
/// </summary> /// </summary>
event EventHandler<FrameRenderedEventArgs> FrameRendered; event EventHandler<FrameRenderedEventArgs> FrameRendered;
} }

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using RGB.NET.Core; using RGB.NET.Core;
using SkiaSharp;
namespace Artemis.Core.Services namespace Artemis.Core.Services
{ {
@ -31,31 +30,25 @@ namespace Artemis.Core.Services
/// </summary> /// </summary>
RGBSurface Surface { get; set; } RGBSurface Surface { get; set; }
/// <summary>
/// Gets the texture brush used to convert the rendered frame to LED-colors
/// </summary>
TextureBrush TextureBrush { get; }
/// <summary>
/// Gets the texture used to convert the rendered frame to LED-colors
/// </summary>
SKTexture? Texture { get; }
/// <summary>
/// Gets the update trigger that drives the render loop
/// </summary>
TimerUpdateTrigger UpdateTrigger { get; }
/// <summary> /// <summary>
/// Gets or sets whether rendering should be paused /// Gets or sets whether rendering should be paused
/// </summary> /// </summary>
bool IsRenderPaused { get; set; } bool IsRenderPaused { get; set; }
/// <summary> /// <summary>
/// Recreates the Texture to use the given <see cref="SKBitmap" /> /// Gets a boolean indicating whether the render pipeline is open
/// </summary> /// </summary>
/// <param name="bitmap"></param> bool RenderOpen { get; }
void UpdateTexture(SKBitmap bitmap);
/// <summary>
/// Opens the render pipeline
/// </summary>
SKTexture OpenRender();
/// <summary>
/// Closes the render pipeline
/// </summary>
void CloseRender();
/// <summary> /// <summary>
/// Adds the given device provider to the <see cref="Surface" /> /// Adds the given device provider to the <see cref="Surface" />

View File

@ -47,15 +47,20 @@ namespace Artemis.Core.Services
{ {
module.Activate(false); module.Activate(false);
// If this is a profile module, activate the last active profile after module activation try
if (module is ProfileModule profileModule) {
await _profileService.ActivateLastProfileAnimated(profileModule); // If this is a profile module, activate the last active profile after module activation
if (module is ProfileModule profileModule)
await _profileService.ActivateLastProfileAnimated(profileModule);
}
catch (Exception e)
{
_logger.Warning(e, $"Failed to activate last profile on module {module}");
}
} }
catch (Exception e) catch (Exception e)
{ {
_logger.Error(new ArtemisPluginFeatureException( _logger.Error(new ArtemisPluginFeatureException(module, "Failed to activate module.", e), "Failed to activate module");
module, "Failed to activate module and last profile.", e), "Failed to activate module and last profile"
);
throw; throw;
} }
} }

View File

@ -17,16 +17,18 @@ namespace Artemis.Core.Services
/// </summary> /// </summary>
internal class RgbService : IRgbService internal class RgbService : IRgbService
{ {
private readonly IDeviceRepository _deviceRepository;
private readonly List<ArtemisDevice> _devices; private readonly List<ArtemisDevice> _devices;
private readonly List<ArtemisDevice> _enabledDevices; private readonly List<ArtemisDevice> _enabledDevices;
private Dictionary<Led, ArtemisLed> _ledMap;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private readonly IDeviceRepository _deviceRepository; private readonly PluginSetting<double> _renderScaleSetting;
private readonly PluginSetting<int> _targetFrameRateSetting; private readonly PluginSetting<int> _targetFrameRateSetting;
private ListLedGroup? _surfaceLedGroup; private readonly TextureBrush _textureBrush = new(ITexture.Empty) {CalculationMode = RenderMode.Absolute};
private Dictionary<Led, ArtemisLed> _ledMap;
private bool _modifyingProviders; private bool _modifyingProviders;
private ListLedGroup? _surfaceLedGroup;
private SKTexture? _texture;
public RgbService(ILogger logger, ISettingsService settingsService, IPluginManagementService pluginManagementService, IDeviceRepository deviceRepository) public RgbService(ILogger logger, ISettingsService settingsService, IPluginManagementService pluginManagementService, IDeviceRepository deviceRepository)
{ {
@ -34,6 +36,7 @@ namespace Artemis.Core.Services
_pluginManagementService = pluginManagementService; _pluginManagementService = pluginManagementService;
_deviceRepository = deviceRepository; _deviceRepository = deviceRepository;
_targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 25); _targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 25);
_renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.5);
Surface = new RGBSurface(); Surface = new RGBSurface();
@ -45,10 +48,72 @@ namespace Artemis.Core.Services
_devices = new List<ArtemisDevice>(); _devices = new List<ArtemisDevice>();
_ledMap = new Dictionary<Led, ArtemisLed>(); _ledMap = new Dictionary<Led, ArtemisLed>();
UpdateTrigger = new TimerUpdateTrigger { UpdateFrequency = 1.0 / _targetFrameRateSetting.Value }; UpdateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / _targetFrameRateSetting.Value};
Surface.RegisterUpdateTrigger(UpdateTrigger); Surface.RegisterUpdateTrigger(UpdateTrigger);
} }
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 SurfaceOnLayoutChanged(SurfaceLayoutChangedEventArgs args)
{
UpdateLedGroup();
}
private void UpdateLedGroup()
{
lock (_devices)
{
if (_modifyingProviders)
return;
_ledMap = new Dictionary<Led, ArtemisLed>(_devices.SelectMany(d => d.Leds).ToDictionary(l => l.RgbLed));
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();
}
}
}
private void TargetFrameRateSettingOnSettingChanged(object? sender, EventArgs e)
{
UpdateTrigger.UpdateFrequency = 1.0 / _targetFrameRateSetting.Value;
}
private void SurfaceOnException(ExceptionEventArgs args)
{
_logger.Warning("Surface threw e");
throw args.Exception;
}
private void OnDeviceAdded(DeviceEventArgs e)
{
DeviceAdded?.Invoke(this, e);
}
public IReadOnlyCollection<ArtemisDevice> EnabledDevices => _enabledDevices.AsReadOnly(); public IReadOnlyCollection<ArtemisDevice> EnabledDevices => _enabledDevices.AsReadOnly();
public IReadOnlyCollection<ArtemisDevice> Devices => _devices.AsReadOnly(); public IReadOnlyCollection<ArtemisDevice> Devices => _devices.AsReadOnly();
public IReadOnlyDictionary<Led, ArtemisLed> LedMap => new ReadOnlyDictionary<Led, ArtemisLed>(_ledMap); public IReadOnlyDictionary<Led, ArtemisLed> LedMap => new ReadOnlyDictionary<Led, ArtemisLed>(_ledMap);
@ -56,11 +121,8 @@ namespace Artemis.Core.Services
/// <inheritdoc /> /// <inheritdoc />
public RGBSurface Surface { get; set; } public RGBSurface Surface { get; set; }
public TimerUpdateTrigger UpdateTrigger { get; }
public TextureBrush TextureBrush { get; private set; } = new(ITexture.Empty) { CalculationMode = RenderMode.Absolute };
public SKTexture? Texture { get; private set; }
public bool IsRenderPaused { get; set; } public bool IsRenderPaused { get; set; }
public bool RenderOpen { get; private set; }
public void AddDeviceProvider(IRGBDeviceProvider deviceProvider) public void AddDeviceProvider(IRGBDeviceProvider deviceProvider)
{ {
@ -88,7 +150,7 @@ namespace Artemis.Core.Services
{ {
ArtemisDevice artemisDevice = GetArtemisDevice(rgbDevice); ArtemisDevice artemisDevice = GetArtemisDevice(rgbDevice);
AddDevice(artemisDevice); AddDevice(artemisDevice);
_logger.Debug("Device provider {deviceProvider} added {deviceName}", deviceProvider.GetType().Name, rgbDevice.DeviceInfo?.DeviceName); _logger.Debug("Device provider {deviceProvider} added {deviceName}", deviceProvider.GetType().Name, rgbDevice.DeviceInfo.DeviceName);
} }
_devices.Sort((a, b) => a.ZIndex - b.ZIndex); _devices.Sort((a, b) => a.ZIndex - b.ZIndex);
@ -134,46 +196,6 @@ namespace Artemis.Core.Services
} }
} }
public void UpdateTexture(SKBitmap bitmap)
{
SKTexture? oldTexture = Texture;
Texture = new SKTexture(bitmap);
TextureBrush.Texture = Texture;
oldTexture?.Dispose();
}
private void SurfaceOnLayoutChanged(SurfaceLayoutChangedEventArgs args) => UpdateLedGroup();
private void UpdateLedGroup()
{
lock (_devices)
{
if (_modifyingProviders)
return;
_ledMap = new Dictionary<Led, ArtemisLed>(_devices.SelectMany(d => d.Leds).ToDictionary(l => l.RgbLed));
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();
}
}
}
#region IDisposable
public void Dispose() public void Dispose()
{ {
@ -183,6 +205,48 @@ namespace Artemis.Core.Services
Surface.Dispose(); Surface.Dispose();
} }
public event EventHandler<DeviceEventArgs>? DeviceAdded;
public event EventHandler<DeviceEventArgs>? DeviceRemoved;
public event EventHandler? LedsChanged;
#region Rendering
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;
}
public void CreateTexture()
{
if (RenderOpen)
throw new ArtemisCoreException("Cannot update the texture while rendering");
SKTexture? oldTexture = _texture;
float renderScale = (float) _renderScaleSetting.Value;
int width = Math.Max(1, MathF.Min(Surface.Boundary.Size.Width * renderScale, 4096).RoundToInt());
int height = Math.Max(1, MathF.Min(Surface.Boundary.Size.Height * renderScale, 4096).RoundToInt());
_texture = new SKTexture(width, height, renderScale);
_textureBrush.Texture = _texture;
oldTexture?.Dispose();
}
#endregion #endregion
#region EnabledDevices #region EnabledDevices
@ -233,7 +297,7 @@ namespace Artemis.Core.Services
private ArtemisLayout LoadDefaultLayout(ArtemisDevice device) private ArtemisLayout LoadDefaultLayout(ArtemisDevice device)
{ {
return new ArtemisLayout("NYI", LayoutSource.Default); return new("NYI", LayoutSource.Default);
} }
public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout, bool createMissingLeds, bool removeExessiveLeds) public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout, bool createMissingLeds, bool removeExessiveLeds)
@ -347,43 +411,5 @@ namespace Artemis.Core.Services
} }
#endregion #endregion
#region Event handlers
private void TargetFrameRateSettingOnSettingChanged(object? sender, EventArgs e)
{
UpdateTrigger.UpdateFrequency = 1.0 / _targetFrameRateSetting.Value;
}
private void SurfaceOnException(ExceptionEventArgs args)
{
_logger.Warning("Surface threw e");
throw args.Exception;
}
#endregion
#region Events
public event EventHandler<DeviceEventArgs>? DeviceAdded;
public event EventHandler<DeviceEventArgs>? DeviceRemoved;
public event EventHandler? LedsChanged;
private void OnDeviceAdded(DeviceEventArgs e)
{
DeviceAdded?.Invoke(this, e);
}
protected virtual void OnDeviceRemoved(DeviceEventArgs e)
{
DeviceRemoved?.Invoke(this, e);
}
protected virtual void OnLedsChanged()
{
LedsChanged?.Invoke(this, EventArgs.Empty);
}
#endregion
} }
} }

View File

@ -94,19 +94,20 @@
StaysOpen="{Binding StaysOpen, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" StaysOpen="{Binding StaysOpen, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
PopupAnimation="Fade" PopupAnimation="Fade"
IsOpen="{Binding PopupOpen, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"> IsOpen="{Binding PopupOpen, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}">
<materialDesign:Card Width="200" Height="230" Margin="30" materialDesign:ShadowAssist.ShadowDepth="Depth4"> <materialDesign:Card Width="200" Height="260" Margin="30" materialDesign:ShadowAssist.ShadowDepth="Depth4">
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="160" /> <RowDefinition Height="*" />
<RowDefinition /> <RowDefinition Height="Auto" />
<RowDefinition /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<materialDesign:ColorPicker Grid.Row="0" <materialDesign:ColorPicker Grid.Row="0"
Color="{Binding Color, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" Color="{Binding Color, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
MouseUp="ColorGradient_OnMouseUp" MouseUp="ColorGradient_OnMouseUp"
PreviewMouseDown="Slider_OnMouseDown" PreviewMouseDown="Slider_OnMouseDown"
PreviewMouseUp="Slider_OnMouseUp" /> PreviewMouseUp="Slider_OnMouseUp" />
<Slider Grid.Row="1" Margin="8" <Slider Grid.Row="1" Margin="8 2 8 8"
IsMoveToPointEnabled="True" IsMoveToPointEnabled="True"
Orientation="Horizontal" Orientation="Horizontal"
Style="{DynamicResource MaterialDesignColorSlider}" Style="{DynamicResource MaterialDesignColorSlider}"
@ -116,16 +117,33 @@
PreviewMouseUp="Slider_OnMouseUp" PreviewMouseUp="Slider_OnMouseUp"
Maximum="255" /> Maximum="255" />
<!-- Checkboxes don't work inside popups inside tree views, which is where the color picker is regularly used --> <ItemsControl Grid.Row="2" x:Name="RecentColorsContainer" Margin="10 0" HorizontalAlignment="Left">
<CheckBox Grid.Row="2" <ItemsControl.ItemTemplate>
<DataTemplate>
<Border CornerRadius="4" Background="{StaticResource Checkerboard}" Width="16" Height="16" Margin="2">
<Rectangle RadiusX="4" RadiusY="4" Cursor="Hand" MouseLeftButtonDown="SelectRecentColor" ToolTip="{Binding}">
<Rectangle.Fill>
<SolidColorBrush Color="{Binding}" />
</Rectangle.Fill>
</Rectangle>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"></WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<CheckBox Grid.Row="3"
x:Name="PreviewCheckBox" x:Name="PreviewCheckBox"
Margin="10 0" Margin="10 5 0 10"
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource MaterialDesignCheckBox}" Style="{StaticResource MaterialDesignCheckBox}"
Click="PreviewCheckBoxClick"> Click="PreviewCheckBoxClick">
Preview on devices Preview on devices
</CheckBox> </CheckBox>
</Grid> </Grid>
</materialDesign:Card> </materialDesign:Card>
</Popup> </Popup>

View File

@ -1,9 +1,11 @@
using System; using System;
using System.Collections.ObjectModel;
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Shapes;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
namespace Artemis.UI.Shared namespace Artemis.UI.Shared
@ -146,6 +148,12 @@ namespace Artemis.UI.Shared
colorPicker._inCallback = true; colorPicker._inCallback = true;
colorPicker.OnPropertyChanged(nameof(PopupOpen)); colorPicker.OnPropertyChanged(nameof(PopupOpen));
if ((bool) e.NewValue)
colorPicker.PopupOpened();
else
colorPicker.PopupClosed();
colorPicker._inCallback = false; colorPicker._inCallback = false;
} }
@ -230,6 +238,23 @@ namespace Artemis.UI.Shared
PreviewCheckBox.IsChecked = _colorPickerService.PreviewSetting.Value; PreviewCheckBox.IsChecked = _colorPickerService.PreviewSetting.Value;
} }
private void PopupClosed()
{
_colorPickerService?.QueueRecentColor(Color);
}
private void PopupOpened()
{
if (_colorPickerService != null)
RecentColorsContainer.ItemsSource = new ObservableCollection<Color>(_colorPickerService.RecentColors);
}
private void SelectRecentColor(object sender, MouseButtonEventArgs e)
{
Color = (Color) ((Rectangle) sender).DataContext;
PopupOpen = false;
}
/// <inheritdoc /> /// <inheritdoc />
public event PropertyChangedEventHandler? PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;

View File

@ -51,7 +51,6 @@ namespace Artemis.UI.Shared
byte g = Led.RgbLed.Color.GetG(); byte g = Led.RgbLed.Color.GetG();
byte b = Led.RgbLed.Color.GetB(); byte b = Led.RgbLed.Color.GetB();
_renderColor.A = (byte) (isDimmed ? 100 : 255);
_renderColor.A = isDimmed ? Dimmed : NonDimmed; _renderColor.A = isDimmed ? Dimmed : NonDimmed;
_renderColor.R = r; _renderColor.R = r;
_renderColor.G = g; _renderColor.G = g;

View File

@ -26,7 +26,7 @@ namespace Artemis.UI.Shared.Input
private DataModelPath? _dataModelPath; private DataModelPath? _dataModelPath;
private DataModelPropertiesViewModel? _dataModelViewModel; private DataModelPropertiesViewModel? _dataModelViewModel;
private bool _displaySwitchButton; private bool _displaySwitchButton;
private Type[] _filterTypes = new Type[0]; private Type[] _filterTypes = Array.Empty<Type>();
private bool _isDataModelViewModelOpen; private bool _isDataModelViewModelOpen;
private bool _isEnabled = true; private bool _isEnabled = true;
private string _placeholder = "Select a property"; private string _placeholder = "Select a property";
@ -143,7 +143,8 @@ namespace Artemis.UI.Shared.Input
if (value) if (value)
{ {
UpdateDataModelVisualization(); UpdateDataModelVisualization();
OpenSelectedValue(DataModelViewModel); if (DataModelViewModel != null)
OpenSelectedValue(DataModelViewModel);
} }
} }
} }
@ -242,7 +243,7 @@ namespace Artemis.UI.Shared.Input
private void ExecuteSelectPropertyCommand(object? context) private void ExecuteSelectPropertyCommand(object? context)
{ {
if (!(context is DataModelVisualizationViewModel selected)) if (context is not DataModelVisualizationViewModel selected)
return; return;
ChangeDataModelPath(selected.DataModelPath); ChangeDataModelPath(selected.DataModelPath);

View File

@ -65,6 +65,9 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
public void RemoveColorStop(ColorStopViewModel colorStopViewModel) public void RemoveColorStop(ColorStopViewModel colorStopViewModel)
{ {
if (colorStopViewModel == null)
return;
ColorStopViewModels.Remove(colorStopViewModel); ColorStopViewModels.Remove(colorStopViewModel);
ColorGradient.Stops.Remove(colorStopViewModel.ColorStop); ColorGradient.Stops.Remove(colorStopViewModel.ColorStop);
ColorGradient.OnColorValuesUpdated(); ColorGradient.OnColorValuesUpdated();

View File

@ -24,9 +24,34 @@ namespace Artemis.UI.Shared.Services
PreviewSetting = settingsService.GetSetting("UI.PreviewColorPickerOnDevices", false); PreviewSetting = settingsService.GetSetting("UI.PreviewColorPickerOnDevices", false);
PreviewSetting.AutoSave = true; PreviewSetting.AutoSave = true;
RecentColorsSetting = settingsService.GetSetting("UI.ColorPickerRecentColors", new LinkedList<Color>());
}
private void RenderColorPickerOverlay(object? sender, FrameRenderingEventArgs e)
{
if (_mustRenderOverlay)
_overlayOpacity += 0.2f;
else
_overlayOpacity -= 0.2f;
if (_overlayOpacity <= 0f)
{
_coreService.FrameRendering -= RenderColorPickerOverlay;
return;
}
if (_overlayOpacity > 1f)
_overlayOpacity = 1f;
using SKPaint overlayPaint = new() {Color = new SKColor(0, 0, 0, (byte) (255 * _overlayOpacity))};
overlayPaint.Color = _overlayColor.WithAlpha((byte) (_overlayColor.Alpha * _overlayOpacity));
e.Canvas.DrawRect(0, 0, e.Canvas.LocalClipBounds.Width, e.Canvas.LocalClipBounds.Height, overlayPaint);
} }
public PluginSetting<bool> PreviewSetting { get; } public PluginSetting<bool> PreviewSetting { get; }
public PluginSetting<LinkedList<Color>> RecentColorsSetting { get; }
public LinkedList<Color> RecentColors => RecentColorsSetting.Value;
public Task<object> ShowGradientPicker(ColorGradient colorGradient, string dialogHost) public Task<object> ShowGradientPicker(ColorGradient colorGradient, string dialogHost)
{ {
@ -56,25 +81,16 @@ namespace Artemis.UI.Shared.Services
_overlayColor = new SKColor(color.R, color.G, color.B, color.A); _overlayColor = new SKColor(color.R, color.G, color.B, color.A);
} }
private void RenderColorPickerOverlay(object? sender, FrameRenderingEventArgs e) public void QueueRecentColor(Color color)
{ {
if (_mustRenderOverlay) if (RecentColors.Contains(color))
_overlayOpacity += 0.2f; RecentColors.Remove(color);
else
_overlayOpacity -= 0.2f;
if (_overlayOpacity <= 0f) RecentColors.AddFirst(color);
{ while (RecentColors.Count > 18)
_coreService.FrameRendering -= RenderColorPickerOverlay; RecentColors.RemoveLast();
return;
}
if (_overlayOpacity > 1f) RecentColorsSetting.Save();
_overlayOpacity = 1f;
using SKPaint overlayPaint = new() {Color = new SKColor(0, 0, 0, (byte) (255 * _overlayOpacity))};
overlayPaint.Color = _overlayColor.WithAlpha((byte) (_overlayColor.Alpha * _overlayOpacity));
e.Canvas.DrawRect(0, 0, e.Canvas.LocalClipBounds.Width, e.Canvas.LocalClipBounds.Height, overlayPaint);
} }
} }
} }

View File

@ -1,4 +1,5 @@
using System.Threading.Tasks; using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Media; using System.Windows.Media;
using Artemis.Core; using Artemis.Core;
@ -9,8 +10,11 @@ namespace Artemis.UI.Shared.Services
Task<object> ShowGradientPicker(ColorGradient colorGradient, string dialogHost); Task<object> ShowGradientPicker(ColorGradient colorGradient, string dialogHost);
PluginSetting<bool> PreviewSetting { get; } PluginSetting<bool> PreviewSetting { get; }
LinkedList<Color> RecentColors { get; }
PluginSetting<LinkedList<Color>> RecentColorsSetting { get; }
void StartColorDisplay(); void StartColorDisplay();
void StopColorDisplay(); void StopColorDisplay();
void UpdateColorDisplay(Color color); void UpdateColorDisplay(Color color);
void QueueRecentColor(Color color);
} }
} }

View File

@ -15,7 +15,7 @@
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
</ResourceDictionary> </ResourceDictionary>
</UserControl.Resources> </UserControl.Resources>
<Grid Margin="5 10 0 0"> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
@ -26,7 +26,8 @@
Background="{StaticResource PrimaryHueMidBrush}" Background="{StaticResource PrimaryHueMidBrush}"
BorderBrush="{StaticResource PrimaryHueMidBrush}" BorderBrush="{StaticResource PrimaryHueMidBrush}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Command="{s:Action AddCondition}"> Command="{s:Action AddCondition}"
Margin="0 5">
ADD CONDITION ADD CONDITION
</Button> </Button>
@ -38,7 +39,7 @@
dd:DragDrop.UseDefaultDragAdorner="True" dd:DragDrop.UseDefaultDragAdorner="True"
HorizontalContentAlignment="Stretch" HorizontalContentAlignment="Stretch"
VirtualizingPanel.ScrollUnit="Pixel" VirtualizingPanel.ScrollUnit="Pixel"
Margin="0 5 0 0"> Margin="-10 0 -10 -5">
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" /> <ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Extensions; using Artemis.UI.Extensions;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
@ -10,16 +11,20 @@ using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.ConditionalDataBinding namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.ConditionalDataBinding
{ {
public sealed class ConditionalDataBindingModeViewModel<TLayerProperty, TProperty> : Conductor<DataBindingConditionViewModel<TLayerProperty, TProperty>>.Collection.AllActive, IDataBindingModeViewModel public sealed class ConditionalDataBindingModeViewModel<TLayerProperty, TProperty> : Conductor<DataBindingConditionViewModel<TLayerProperty, TProperty>>.Collection.AllActive,
IDataBindingModeViewModel
{ {
private readonly IDataBindingsVmFactory _dataBindingsVmFactory; private readonly IDataBindingsVmFactory _dataBindingsVmFactory;
private readonly ICoreService _coreService;
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private bool _updating; private bool _updating;
public ConditionalDataBindingModeViewModel(ConditionalDataBinding<TLayerProperty, TProperty> conditionalDataBinding, public ConditionalDataBindingModeViewModel(ConditionalDataBinding<TLayerProperty, TProperty> conditionalDataBinding,
ICoreService coreService,
IProfileEditorService profileEditorService, IProfileEditorService profileEditorService,
IDataBindingsVmFactory dataBindingsVmFactory) IDataBindingsVmFactory dataBindingsVmFactory)
{ {
_coreService = coreService;
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
_dataBindingsVmFactory = dataBindingsVmFactory; _dataBindingsVmFactory = dataBindingsVmFactory;
@ -41,8 +46,21 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.Conditio
protected override void OnInitialActivate() protected override void OnInitialActivate()
{ {
base.OnInitialActivate();
Initialize(); Initialize();
_coreService.FrameRendered += CoreServiceOnFrameRendered;
base.OnInitialActivate();
}
protected override void OnClose()
{
_coreService.FrameRendered -= CoreServiceOnFrameRendered;
base.OnClose();
}
private void CoreServiceOnFrameRendered(object sender, FrameRenderedEventArgs e)
{
foreach (DataBindingConditionViewModel<TLayerProperty, TProperty> dataBindingConditionViewModel in Items)
dataBindingConditionViewModel.Evaluate();
} }
private void UpdateItems() private void UpdateItems()

View File

@ -56,6 +56,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.Conditio
_profileEditorService.UpdateSelectedProfileElement(); _profileEditorService.UpdateSelectedProfileElement();
} }
public void Evaluate()
{
ActiveItem?.Evaluate();
}
#region IDisposable #region IDisposable
/// <inheritdoc /> /// <inheritdoc />

View File

@ -64,42 +64,29 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs
private void CoreServiceOnFrameRendered(object sender, FrameRenderedEventArgs e) private void CoreServiceOnFrameRendered(object sender, FrameRenderedEventArgs e)
{ {
Execute.PostToUIThread(() => Execute.OnUIThreadSync(() =>
{ {
if (e.Texture.Bitmap.Pixels.Length == 0)
return;
SKImageInfo bitmapInfo = e.Texture.Bitmap.Info; SKImageInfo bitmapInfo = e.Texture.Bitmap.Info;
RenderHeight = bitmapInfo.Height; RenderHeight = bitmapInfo.Height;
RenderWidth = bitmapInfo.Width; RenderWidth = bitmapInfo.Width;
if (!(CurrentFrame is WriteableBitmap writeableBitmap) || // ReSharper disable twice CompareOfFloatsByEqualityOperator
writeableBitmap.Width != bitmapInfo.Width || if (CurrentFrame is not WriteableBitmap writable || writable.Width != bitmapInfo.Width || writable.Height != bitmapInfo.Height)
writeableBitmap.Height != bitmapInfo.Height)
{ {
CurrentFrame = e.Texture.Bitmap.ToWriteableBitmap(); CurrentFrame = e.Texture.Bitmap.ToWriteableBitmap();
return; return;
} }
try using SKImage skImage = SKImage.FromPixels(e.Texture.Bitmap.PeekPixels());
SKImageInfo info = new(skImage.Width, skImage.Height);
writable.Lock();
using (SKPixmap pixmap = new(info, writable.BackBuffer, writable.BackBufferStride))
{ {
using (SKImage skiaImage = SKImage.FromPixels(e.Texture.Bitmap.PeekPixels())) skImage.ReadPixels(pixmap, 0, 0);
{ }
SKImageInfo info = new(skiaImage.Width, skiaImage.Height);
writeableBitmap.Lock();
using (SKPixmap pixmap = new(info, writeableBitmap.BackBuffer, writeableBitmap.BackBufferStride))
{
skiaImage.ReadPixels(pixmap, 0, 0);
}
writeableBitmap.AddDirtyRect(new Int32Rect(0, 0, writeableBitmap.PixelWidth, writeableBitmap.PixelHeight)); writable.AddDirtyRect(new Int32Rect(0, 0, writable.PixelWidth, writable.PixelHeight));
writeableBitmap.Unlock(); writable.Unlock();
}
}
catch (AccessViolationException)
{
// oops
}
}); });
} }

View File

@ -192,7 +192,7 @@ namespace Artemis.UI.Screens.Settings.Device
#region Event handlers #region Event handlers
private void DeviceOnDeviceUpdated(object? sender, EventArgs e) private void DeviceOnDeviceUpdated(object sender, EventArgs e)
{ {
NotifyOfPropertyChange(nameof(CanExportLayout)); NotifyOfPropertyChange(nameof(CanExportLayout));
} }

View File

@ -209,6 +209,13 @@
ToolTip="If selected, each device is completely lid up with a random color"> ToolTip="If selected, each device is completely lid up with a random color">
Show random device colors Show random device colors
</CheckBox> </CheckBox>
<CheckBox Style="{StaticResource MaterialDesignCheckBox}"
IsChecked="{Binding ColorFirstLedOnly}"
IsEnabled="{Binding ColorDevices}"
ToolTip="If selected, only the first LED of each device is lid up, aiding with rotation settings"
Margin="5 0 0 0">
Light up first LED only
</CheckBox>
</StackPanel> </StackPanel>
</materialDesign:Card> </materialDesign:Card>
</StackPanel> </StackPanel>

View File

@ -92,7 +92,18 @@ namespace Artemis.UI.Screens.SurfaceEditor
public bool ColorDevices public bool ColorDevices
{ {
get => _colorDevices; get => _colorDevices;
set => SetAndNotify(ref _colorDevices, value); set
{
SetAndNotify(ref _colorDevices, value);
if (!value)
ColorFirstLedOnly = false;
}
}
public bool ColorFirstLedOnly
{
get => _colorFirstLedOnly;
set => SetAndNotify(ref _colorFirstLedOnly, value);
} }
public void OpenHyperlink(object sender, RequestNavigateEventArgs e) public void OpenHyperlink(object sender, RequestNavigateEventArgs e)
@ -125,10 +136,17 @@ namespace Artemis.UI.Screens.SurfaceEditor
if (!ColorDevices) if (!ColorDevices)
return; return;
e.Canvas.Clear(new SKColor(0, 0, 0));
foreach (ListDeviceViewModel listDeviceViewModel in ListDeviceViewModels) foreach (ListDeviceViewModel listDeviceViewModel in ListDeviceViewModels)
{ {
foreach (ArtemisLed artemisLed in listDeviceViewModel.Device.Leds) // Order by position to accurately get the first LED
e.Canvas.DrawRect(artemisLed.AbsoluteRectangle, new SKPaint {Color = listDeviceViewModel.Color}); List<ArtemisLed> leds = listDeviceViewModel.Device.Leds.OrderBy(l => l.RgbLed.Location.Y).ThenBy(l => l.RgbLed.Location.X).ToList();
for (int index = 0; index < leds.Count; index++)
{
ArtemisLed artemisLed = leds[index];
if (ColorFirstLedOnly && index == 0 || !ColorFirstLedOnly)
e.Canvas.DrawRect(artemisLed.AbsoluteRectangle, new SKPaint {Color = listDeviceViewModel.Color});
}
} }
} }
@ -258,6 +276,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
private MouseDragStatus _mouseDragStatus; private MouseDragStatus _mouseDragStatus;
private Point _mouseDragStartPoint; private Point _mouseDragStartPoint;
private bool _colorDevices; private bool _colorDevices;
private bool _colorFirstLedOnly;
// ReSharper disable once UnusedMember.Global - Called from view // ReSharper disable once UnusedMember.Global - Called from view
public void EditorGridMouseClick(object sender, MouseButtonEventArgs e) public void EditorGridMouseClick(object sender, MouseButtonEventArgs e)