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:
commit
eeaa374d40
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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" />
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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" />
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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 />
|
||||||
|
|||||||
@ -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
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user