1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Merge branch 'development'

This commit is contained in:
Robert 2021-04-21 17:26:07 +02:00
commit 6528775530
12 changed files with 343 additions and 146 deletions

View File

@ -4,20 +4,20 @@ using Artemis.Core.DataModelExpansions;
namespace Artemis.Core namespace Artemis.Core
{ {
/// <summary> /// <summary>
/// Provides data about dynamic data model related events /// Provides data about dynamic data model child related events
/// </summary> /// </summary>
public class DynamicDataModelEventArgs : EventArgs public class DynamicDataModelChildEventArgs : EventArgs
{ {
internal DynamicDataModelEventArgs(DataModel dynamicDataModel, string key) internal DynamicDataModelChildEventArgs(DynamicChild dynamicChild, string key)
{ {
DynamicDataModel = dynamicDataModel; DynamicChild = dynamicChild;
Key = key; Key = key;
} }
/// <summary> /// <summary>
/// Gets the dynamic data model /// Gets the dynamic data model child
/// </summary> /// </summary>
public DataModel DynamicDataModel { get; } public DynamicChild DynamicChild { get; }
/// <summary> /// <summary>
/// Gets the key of the dynamic data model on the parent <see cref="DataModel" /> /// Gets the key of the dynamic data model on the parent <see cref="DataModel" />

View File

@ -15,6 +15,8 @@ namespace Artemis.Core
{ {
private Expression<Func<object, object>>? _accessorLambda; private Expression<Func<object, object>>? _accessorLambda;
private DataModel? _dynamicDataModel; private DataModel? _dynamicDataModel;
private Type? _dynamicDataModelType;
private DataModelPropertyAttribute? _dynamicDataModelAttribute;
internal DataModelPathSegment(DataModelPath dataModelPath, string identifier, string path) internal DataModelPathSegment(DataModelPath dataModelPath, string identifier, string path)
{ {
@ -49,12 +51,6 @@ namespace Artemis.Core
/// </summary> /// </summary>
public DataModelPathSegmentType Type { get; private set; } public DataModelPathSegmentType Type { get; private set; }
/// <summary>
/// Gets the type of dynamic data model this path points to
/// <para>Not used if the <see cref="Type" /> is <see cref="DataModelPathSegmentType.Static" /></para>
/// </summary>
public Type? DynamicDataModelType { get; private set; }
/// <summary> /// <summary>
/// Gets the previous segment in the path /// Gets the previous segment in the path
/// </summary> /// </summary>
@ -114,7 +110,7 @@ namespace Artemis.Core
{ {
// Dynamic types have a data model description // Dynamic types have a data model description
if (Type == DataModelPathSegmentType.Dynamic) if (Type == DataModelPathSegmentType.Dynamic)
return (GetValue() as DataModel)?.DataModelDescription; return _dynamicDataModelAttribute;
if (IsStartSegment && DataModelPath.Target != null) if (IsStartSegment && DataModelPath.Target != null)
return DataModelPath.Target.DataModelDescription; return DataModelPath.Target.DataModelDescription;
if (IsStartSegment) if (IsStartSegment)
@ -187,12 +183,12 @@ namespace Artemis.Core
return CreateExpression(parameter, expression, nullCondition); return CreateExpression(parameter, expression, nullCondition);
// If a dynamic data model is found the use that // If a dynamic data model is found the use that
bool hasDynamicDataModel = _dynamicDataModel.DynamicDataModels.TryGetValue(Identifier, out DataModel? dynamicDataModel); bool hasDynamicChild = _dynamicDataModel.DynamicChildren.TryGetValue(Identifier, out DynamicChild? dynamicChild);
if (hasDynamicDataModel && dynamicDataModel != null) if (hasDynamicChild && dynamicChild?.BaseValue != null)
DetermineDynamicType(dynamicDataModel); DetermineDynamicType(dynamicChild.BaseValue, dynamicChild.Attribute);
_dynamicDataModel.DynamicDataModelAdded += DynamicDataModelOnDynamicDataModelAdded; _dynamicDataModel.DynamicChildAdded += DynamicChildOnDynamicChildAdded;
_dynamicDataModel.DynamicDataModelRemoved += DynamicDataModelOnDynamicDataModelRemoved; _dynamicDataModel.DynamicChildRemoved += DynamicChildOnDynamicChildRemoved;
} }
return CreateExpression(parameter, expression, nullCondition); return CreateExpression(parameter, expression, nullCondition);
@ -216,12 +212,14 @@ namespace Artemis.Core
accessorExpression = Expression.PropertyOrField(expression, Identifier); accessorExpression = Expression.PropertyOrField(expression, Identifier);
// A dynamic segment calls the generic method DataModel.DynamicChild<T> and provides the identifier as an argument // A dynamic segment calls the generic method DataModel.DynamicChild<T> and provides the identifier as an argument
else else
{
accessorExpression = Expression.Call( accessorExpression = Expression.Call(
expression, expression,
nameof(DataModel.DynamicChild), nameof(DataModel.GetDynamicChildValue),
DynamicDataModelType != null ? new[] {DynamicDataModelType} : null, _dynamicDataModelType != null ? new[] { _dynamicDataModelType } : null,
Expression.Constant(Identifier) Expression.Constant(Identifier)
); );
}
_accessorLambda = Expression.Lambda<Func<object, object>>( _accessorLambda = Expression.Lambda<Func<object, object>>(
// Wrap with a null check // Wrap with a null check
@ -236,10 +234,11 @@ namespace Artemis.Core
return accessorExpression; return accessorExpression;
} }
private void DetermineDynamicType(DataModel dynamicDataModel) private void DetermineDynamicType(object dynamicDataModel, DataModelPropertyAttribute attribute)
{ {
Type = DataModelPathSegmentType.Dynamic; Type = DataModelPathSegmentType.Dynamic;
DynamicDataModelType = dynamicDataModel.GetType(); _dynamicDataModelType = dynamicDataModel.GetType();
_dynamicDataModelAttribute = attribute;
} }
private void DetermineStaticType(Type previousType) private void DetermineStaticType(Type previousType)
@ -263,8 +262,8 @@ namespace Artemis.Core
{ {
if (_dynamicDataModel != null) if (_dynamicDataModel != null)
{ {
_dynamicDataModel.DynamicDataModelAdded -= DynamicDataModelOnDynamicDataModelAdded; _dynamicDataModel.DynamicChildAdded -= DynamicChildOnDynamicChildAdded;
_dynamicDataModel.DynamicDataModelRemoved -= DynamicDataModelOnDynamicDataModelRemoved; _dynamicDataModel.DynamicChildRemoved -= DynamicChildOnDynamicChildRemoved;
} }
Type = DataModelPathSegmentType.Invalid; Type = DataModelPathSegmentType.Invalid;
@ -285,15 +284,15 @@ namespace Artemis.Core
#region Event handlers #region Event handlers
private void DynamicDataModelOnDynamicDataModelAdded(object? sender, DynamicDataModelEventArgs e) private void DynamicChildOnDynamicChildAdded(object? sender, DynamicDataModelChildEventArgs e)
{ {
if (e.Key == Identifier) if (e.Key == Identifier)
DataModelPath.Initialize(); DataModelPath.Initialize();
} }
private void DynamicDataModelOnDynamicDataModelRemoved(object? sender, DynamicDataModelEventArgs e) private void DynamicChildOnDynamicChildRemoved(object? sender, DynamicDataModelChildEventArgs e)
{ {
if (e.DynamicDataModel == _dynamicDataModel) if (e.DynamicChild.BaseValue == _dynamicDataModel)
DataModelPath.Initialize(); DataModelPath.Initialize();
} }

View File

@ -195,7 +195,7 @@ namespace Artemis.Core
baseLayerEffect.Update(Timeline.Delta.TotalSeconds); baseLayerEffect.Update(Timeline.Delta.TotalSeconds);
} }
SKPaint layerPaint = new(); SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low};
try try
{ {
SKRectI rendererBounds = SKRectI.Create(0, 0, Bounds.Width, Bounds.Height); SKRectI rendererBounds = SKRectI.Create(0, 0, Bounds.Width, Bounds.Height);

View File

@ -364,7 +364,7 @@ namespace Artemis.Core
if (LayerBrush?.BrushType != LayerBrushType.Regular) if (LayerBrush?.BrushType != LayerBrushType.Regular)
return; return;
SKPaint layerPaint = new(); SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low};
try try
{ {
canvas.Save(); canvas.Save();

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Artemis.Core.Modules; using Artemis.Core.Modules;
@ -14,7 +15,7 @@ namespace Artemis.Core.DataModelExpansions
/// </summary> /// </summary>
public abstract class DataModel public abstract class DataModel
{ {
private readonly Dictionary<string, DataModel> _dynamicDataModels = new(); private readonly Dictionary<string, DynamicChild> _dynamicChildren = new();
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="DataModel" /> class /// Creates a new instance of the <see cref="DataModel" /> class
@ -47,10 +48,10 @@ namespace Artemis.Core.DataModelExpansions
public bool IsExpansion { get; internal set; } public bool IsExpansion { get; internal set; }
/// <summary> /// <summary>
/// Gets an read-only dictionary of all dynamic data models /// Gets an read-only dictionary of all dynamic children
/// </summary> /// </summary>
[DataModelIgnore] [DataModelIgnore]
public ReadOnlyDictionary<string, DataModel> DynamicDataModels => new(_dynamicDataModels); public ReadOnlyDictionary<string, DynamicChild> DynamicChildren => new(_dynamicChildren);
/// <summary> /// <summary>
/// Returns a read-only collection of all properties in this datamodel that are to be ignored /// Returns a read-only collection of all properties in this datamodel that are to be ignored
@ -67,122 +68,213 @@ namespace Artemis.Core.DataModelExpansions
} }
/// <summary> /// <summary>
/// Adds a dynamic data model to this data model /// Occurs when a dynamic child has been added to this data model
/// </summary> /// </summary>
/// <param name="dynamicDataModel">The dynamic data model to add</param> public event EventHandler<DynamicDataModelChildEventArgs>? DynamicChildAdded;
/// <param name="key">The key of the child, must be unique to this data model</param>
/// <param name="name">An optional name, if not provided the key will be used in a humanized form</param> /// <summary>
/// <param name="description">An optional description</param> /// Occurs when a dynamic child has been removed from this data model
public T AddDynamicChild<T>(T dynamicDataModel, string key, string? name = null, string? description = null) where T : DataModel /// </summary>
public event EventHandler<DynamicDataModelChildEventArgs>? DynamicChildRemoved;
/// <summary>
/// Invokes the <see cref="DynamicChildAdded" /> event
/// </summary>
protected virtual void OnDynamicDataModelAdded(DynamicDataModelChildEventArgs e)
{ {
if (dynamicDataModel == null) DynamicChildAdded?.Invoke(this, e);
throw new ArgumentNullException(nameof(dynamicDataModel)); }
if (key == null)
throw new ArgumentNullException(nameof(key)); /// <summary>
/// Invokes the <see cref="DynamicChildRemoved" /> event
/// </summary>
protected virtual void OnDynamicDataModelRemoved(DynamicDataModelChildEventArgs e)
{
DynamicChildRemoved?.Invoke(this, e);
}
#region Dynamic children
/// <summary>
/// Adds a dynamic child to this data model
/// </summary>
/// <param name="key">The key of the child, must be unique to this data model</param>
/// <param name="initialValue">The initial value of the dynamic child</param>
/// <returns>The resulting dynamic child which can be used to further update the value</returns>
public DynamicChild<T> AddDynamicChild<T>(string key, T initialValue)
{
return AddDynamicChild(key, initialValue, new DataModelPropertyAttribute());
}
/// <summary>
/// Adds a dynamic child to this data model
/// </summary>
/// <param name="key">The key of the child, must be unique to this data model</param>
/// <param name="initialValue">The initial value of the dynamic child</param>
/// <param name="name">A human readable name for your dynamic child, shown in the UI</param>
/// <param name="description">An optional description, shown in the UI</param>
/// <returns>The resulting dynamic child which can be used to further update the value</returns>
public DynamicChild<T> AddDynamicChild<T>(string key, T initialValue, string name, string? description = null)
{
return AddDynamicChild(key, initialValue, new DataModelPropertyAttribute {Name = name, Description = description});
}
/// <summary>
/// Adds a dynamic child to this data model
/// </summary>
/// <param name="key">The key of the child, must be unique to this data model</param>
/// <param name="initialValue">The initial value of the dynamic child</param>
/// <param name="attribute">A data model property attribute describing the dynamic child</param>
/// <returns>The resulting dynamic child which can be used to further update the value</returns>
public DynamicChild<T> AddDynamicChild<T>(string key, T initialValue, DataModelPropertyAttribute attribute)
{
if (key == null) throw new ArgumentNullException(nameof(key));
if (initialValue == null) throw new ArgumentNullException(nameof(initialValue));
if (attribute == null) throw new ArgumentNullException(nameof(attribute));
if (key.Contains('.')) if (key.Contains('.'))
throw new ArtemisCoreException("The provided key contains an illegal character (.)"); throw new ArtemisCoreException("The provided key contains an illegal character (.)");
if (_dynamicDataModels.ContainsKey(key)) if (_dynamicChildren.ContainsKey(key))
throw new ArtemisCoreException($"Cannot add a dynamic data model with key '{key}' " +
"because the key is already in use on by another dynamic property this data model.");
if (_dynamicDataModels.ContainsValue(dynamicDataModel))
{ {
string existingKey = _dynamicDataModels.First(kvp => kvp.Value == dynamicDataModel).Key; throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " +
throw new ArtemisCoreException($"Cannot add a dynamic data model with key '{key}' " + "because the key is already in use on by another dynamic property this data model.");
$"because the dynamic data model is already added with key '{existingKey}.");
} }
if (GetType().GetProperty(key) != null) if (GetType().GetProperty(key) != null)
throw new ArtemisCoreException($"Cannot add a dynamic data model with key '{key}' " +
"because the key is already in use by a static property on this data model.");
dynamicDataModel.Feature = Feature;
dynamicDataModel.DataModelDescription = new DataModelPropertyAttribute
{ {
Name = string.IsNullOrWhiteSpace(name) ? key.Humanize() : name, throw new ArtemisCoreException($"Cannot add a dynamic child with key '{key}' " +
Description = description "because the key is already in use by a static property on this data model.");
}; }
_dynamicDataModels.Add(key, dynamicDataModel);
OnDynamicDataModelAdded(new DynamicDataModelEventArgs(dynamicDataModel, key)); // Make sure a name is on the attribute or funny things might happen
return dynamicDataModel; attribute.Name ??= key.Humanize();
if (initialValue is DataModel dynamicDataModel)
{
dynamicDataModel.Feature = Feature;
dynamicDataModel.DataModelDescription = attribute;
}
DynamicChild<T> dynamicChild = new(initialValue, key, attribute);
_dynamicChildren.Add(key, dynamicChild);
OnDynamicDataModelAdded(new DynamicDataModelChildEventArgs(dynamicChild, key));
return dynamicChild;
} }
/// <summary> /// <summary>
/// Removes a dynamic data model from the data model by its key /// Gets a previously added dynamic child by its key
/// </summary> /// </summary>
/// <param name="key">The key of the dynamic data model to remove</param> /// <param name="key">The key of the dynamic child</param>
public DynamicChild GetDynamicChild(string key)
{
if (key == null) throw new ArgumentNullException(nameof(key));
return DynamicChildren[key];
}
/// <summary>
/// Gets a previously added dynamic child by its key
/// </summary>
/// <typeparam name="T">The typer of dynamic child you are expecting</typeparam>
/// <param name="key">The key of the dynamic child</param>
/// <returns></returns>
public DynamicChild<T> GetDynamicChild<T>(string key)
{
if (key == null) throw new ArgumentNullException(nameof(key));
return (DynamicChild<T>) DynamicChildren[key];
}
/// <summary>
/// Gets a previously added dynamic child by its key
/// </summary>
/// <param name="key">The key of the dynamic child</param>
/// <param name="dynamicChild">
/// When this method returns, the <see cref="DynamicChild" /> associated with the specified key,
/// if the key is found; otherwise, <see langword="null" />. This parameter is passed uninitialized.
/// </param>
/// <returns>
/// <see langword="true" /> if the data model contains the dynamic child; otherwise <see langword="false" />
/// </returns>
public bool TryGetDynamicChild(string key, [MaybeNullWhen(false)] out DynamicChild dynamicChild)
{
if (key == null) throw new ArgumentNullException(nameof(key));
dynamicChild = null;
if (!DynamicChildren.TryGetValue(key, out DynamicChild? value))
return false;
dynamicChild = value;
return true;
}
/// <summary>
/// Gets a previously added dynamic child by its key
/// </summary>
/// <typeparam name="T">The typer of dynamic child you are expecting</typeparam>
/// <param name="key">The key of the dynamic child</param>
/// <param name="dynamicChild">
/// When this method returns, the <see cref="DynamicChild{T}" /> associated with the specified
/// key, if the key is found and the type matches; otherwise, <see langword="null" />. This parameter is passed
/// uninitialized.
/// </param>
/// <returns>
/// <see langword="true" /> if the data model contains the dynamic child; otherwise <see langword="false" />
/// </returns>
public bool TryGetDynamicChild<T>(string key, [MaybeNullWhen(false)] out DynamicChild<T> dynamicChild)
{
if (key == null) throw new ArgumentNullException(nameof(key));
dynamicChild = null;
if (!DynamicChildren.TryGetValue(key, out DynamicChild? value))
return false;
if (value is not DynamicChild<T> typedDynamicChild)
return false;
dynamicChild = typedDynamicChild;
return true;
}
/// <summary>
/// Removes a dynamic child from the data model by its key
/// </summary>
/// <param name="key">The key of the dynamic child to remove</param>
public void RemoveDynamicChildByKey(string key) public void RemoveDynamicChildByKey(string key)
{ {
_dynamicDataModels.TryGetValue(key, out DataModel? childDataModel); if (key == null) throw new ArgumentNullException(nameof(key));
if (childDataModel == null) if (!_dynamicChildren.TryGetValue(key, out DynamicChild? dynamicChild))
return; return;
_dynamicDataModels.Remove(key); _dynamicChildren.Remove(key);
OnDynamicDataModelRemoved(new DynamicDataModelEventArgs(childDataModel, key)); OnDynamicDataModelRemoved(new DynamicDataModelChildEventArgs(dynamicChild, key));
} }
/// <summary> /// <summary>
/// Removes a dynamic data model from this data model /// Removes a dynamic child from this data model
/// </summary> /// </summary>
/// <param name="dynamicDataModel">The dynamic data model to remove</param> /// <param name="dynamicChild">The dynamic data child to remove</param>
public void RemoveDynamicChild(DataModel dynamicDataModel) public void RemoveDynamicChild(DynamicChild dynamicChild)
{ {
List<string> keys = _dynamicDataModels.Where(kvp => kvp.Value == dynamicDataModel).Select(kvp => kvp.Key).ToList(); if (dynamicChild == null) throw new ArgumentNullException(nameof(dynamicChild));
List<string> keys = _dynamicChildren.Where(kvp => kvp.Value.BaseValue == dynamicChild).Select(kvp => kvp.Key).ToList();
foreach (string key in keys) foreach (string key in keys)
{ {
_dynamicDataModels.Remove(key); _dynamicChildren.Remove(key);
OnDynamicDataModelRemoved(new DynamicDataModelEventArgs(dynamicDataModel, key)); OnDynamicDataModelRemoved(new DynamicDataModelChildEventArgs(dynamicChild, key));
} }
} }
/// <summary> /// <summary>
/// Removes all dynamic data models from this data model /// Removes all dynamic children from this data model
/// </summary> /// </summary>
public void ClearDynamicChildren() public void ClearDynamicChildren()
{ {
while (_dynamicDataModels.Any()) while (_dynamicChildren.Any())
RemoveDynamicChildByKey(_dynamicDataModels.First().Key); RemoveDynamicChildByKey(_dynamicChildren.First().Key);
} }
/// <summary> // Used a runtime by data model paths only
/// Gets a dynamic data model of type <typeparamref name="T" /> by its key internal T? GetDynamicChildValue<T>(string key)
/// </summary>
/// <typeparam name="T">The type of data model you expect</typeparam>
/// <param name="key">The unique key of the dynamic data model</param>
/// <returns>If found, the dynamic data model otherwise <c>null</c></returns>
public T? DynamicChild<T>(string key) where T : DataModel
{ {
_dynamicDataModels.TryGetValue(key, out DataModel? value); if (TryGetDynamicChild(key, out DynamicChild? dynamicChild) && dynamicChild.BaseValue != null)
return value as T; return (T) dynamicChild.BaseValue;
} return default;
#region Events
/// <summary>
/// Occurs when a dynamic data model has been added to this data model
/// </summary>
public event EventHandler<DynamicDataModelEventArgs>? DynamicDataModelAdded;
/// <summary>
/// Occurs when a dynamic data model has been removed from this data model
/// </summary>
public event EventHandler<DynamicDataModelEventArgs>? DynamicDataModelRemoved;
/// <summary>
/// Invokes the <see cref="DynamicDataModelAdded" /> event
/// </summary>
protected virtual void OnDynamicDataModelAdded(DynamicDataModelEventArgs e)
{
DynamicDataModelAdded?.Invoke(this, e);
}
/// <summary>
/// Invokes the <see cref="DynamicDataModelRemoved" /> event
/// </summary>
protected virtual void OnDynamicDataModelRemoved(DynamicDataModelEventArgs e)
{
DynamicDataModelRemoved?.Invoke(this, e);
} }
#endregion #endregion

View File

@ -0,0 +1,66 @@
using System;
namespace Artemis.Core.DataModelExpansions
{
/// <summary>
/// Represents a dynamic child value with its property attribute
/// </summary>
public class DynamicChild<T> : DynamicChild
{
internal DynamicChild(T value, string key, DataModelPropertyAttribute attribute) : base(key, attribute, typeof(T))
{
Value = value;
}
/// <summary>
/// Gets or sets the current value of the dynamic child
/// </summary>
public new T Value { get; set; }
/// <inheritdoc />
protected override object? GetValue()
{
return Value;
}
}
/// <summary>
/// Represents a dynamic child value with its property attribute
/// </summary>
public abstract class DynamicChild
{
internal DynamicChild(string key, DataModelPropertyAttribute attribute, Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
Key = key ?? throw new ArgumentNullException(nameof(key));
Attribute = attribute ?? throw new ArgumentNullException(nameof(attribute));
Type = type;
}
/// <summary>
/// Gets the key of the dynamic child
/// </summary>
public string Key { get; }
/// <summary>
/// Gets the attribute describing the dynamic child
/// </summary>
public DataModelPropertyAttribute Attribute { get; }
/// <summary>
/// Gets the type of <see cref="BaseValue" />
/// </summary>
public Type Type { get; }
/// <summary>
/// Gets the current value of the dynamic child
/// </summary>
public object? BaseValue => GetValue();
/// <summary>
/// Gets the current value of the dynamic child
/// </summary>
/// <returns>The current value of the dynamic child</returns>
protected abstract object? GetValue();
}
}

View File

@ -113,7 +113,7 @@ namespace Artemis.Core.Services
private void SurfaceOnException(ExceptionEventArgs args) private void SurfaceOnException(ExceptionEventArgs args)
{ {
_logger.Warning("Surface threw e"); _logger.Warning(args.Exception, "Surface caught exception");
throw args.Exception; throw args.Exception;
} }

View File

@ -314,6 +314,7 @@ namespace Artemis.UI.Shared
ImageBrush bitmapBrush = new(bitmap); ImageBrush bitmapBrush = new(bitmap);
bitmapBrush.Freeze(); bitmapBrush.Freeze();
_backingStore.OpacityMask = bitmapBrush; _backingStore.OpacityMask = bitmapBrush;
_backingStore.Children.Clear();
InvalidateMeasure(); InvalidateMeasure();
} }
@ -331,14 +332,19 @@ namespace Artemis.UI.Shared
private void Render() private void Render()
{ {
DrawingContext drawingContext = _backingStore.Append(); DrawingContext drawingContext = _backingStore.Append();
// DrawingContext drawingContext = _backingStore.Open();
if (HighlightedLeds != null && HighlightedLeds.Any()) if (HighlightedLeds != null && HighlightedLeds.Any())
{
// Faster on large devices, maybe a bit slower on smaller ones but that's ok
ILookup<ArtemisLed, ArtemisLed> toHighlight = HighlightedLeds.ToLookup(l => l);
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds) foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
deviceVisualizerLed.RenderColor(_backingStore, drawingContext, !HighlightedLeds.Contains(deviceVisualizerLed.Led)); deviceVisualizerLed.RenderColor(_backingStore, drawingContext, !toHighlight.Contains(deviceVisualizerLed.Led));
}
else else
{
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds) foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
deviceVisualizerLed.RenderColor(_backingStore, drawingContext, false); deviceVisualizerLed.RenderColor(_backingStore, drawingContext, false);
}
drawingContext.Close(); drawingContext.Close();
} }

View File

@ -14,10 +14,10 @@ namespace Artemis.UI.Shared
{ {
private const byte Dimmed = 100; private const byte Dimmed = 100;
private const byte NonDimmed = 255; private const byte NonDimmed = 255;
private GeometryDrawing? _geometryDrawing;
private Color _renderColor; private Color _renderColor;
private GeometryDrawing? _geometryDrawing;
private SolidColorBrush? _renderColorBrush; private SolidColorBrush? _renderColorBrush;
private DrawingGroup? _lastBackingStore;
public DeviceVisualizerLed(ArtemisLed led) public DeviceVisualizerLed(ArtemisLed led)
{ {
@ -43,7 +43,7 @@ namespace Artemis.UI.Shared
public void RenderColor(DrawingGroup backingStore, DrawingContext drawingContext, bool isDimmed) public void RenderColor(DrawingGroup backingStore, DrawingContext drawingContext, bool isDimmed)
{ {
if (DisplayGeometry == null) if (DisplayGeometry == null || backingStore == null)
return; return;
_renderColorBrush ??= new SolidColorBrush(); _renderColorBrush ??= new SolidColorBrush();
@ -59,9 +59,12 @@ namespace Artemis.UI.Shared
_renderColor.B = b; _renderColor.B = b;
_renderColorBrush.Color = _renderColor; _renderColorBrush.Color = _renderColor;
if (_lastBackingStore != backingStore)
if (!backingStore.Children.Contains(_geometryDrawing)) {
backingStore.Children.Add(_geometryDrawing); backingStore.Children.Add(_geometryDrawing);
_lastBackingStore = backingStore;
}
} }
public void RenderImage(DrawingContext drawingContext) public void RenderImage(DrawingContext drawingContext)

View File

@ -239,9 +239,10 @@ namespace Artemis.UI.Shared
// Add missing dynamic children // Add missing dynamic children
object? value = Parent == null || Parent.IsRootViewModel ? DataModel : DataModelPath?.GetValue(); object? value = Parent == null || Parent.IsRootViewModel ? DataModel : DataModelPath?.GetValue();
if (value is DataModel dataModel) if (value is DataModel dataModel)
foreach (KeyValuePair<string, DataModel> kvp in dataModel.DynamicDataModels) {
foreach (var (key, dynamicChild) in dataModel.DynamicChildren)
{ {
string childPath = AppendToPath(kvp.Key); string childPath = AppendToPath(key);
if (Children.Any(c => c.Path != null && c.Path.Equals(childPath))) if (Children.Any(c => c.Path != null && c.Path.Equals(childPath)))
continue; continue;
@ -249,6 +250,7 @@ namespace Artemis.UI.Shared
if (child != null) if (child != null)
Children.Add(child); Children.Add(child);
} }
}
// Remove dynamic children that have been removed from the data model // Remove dynamic children that have been removed from the data model
List<DataModelVisualizationViewModel> toRemoveDynamic = Children.Where(c => c.DataModelPath != null && !c.DataModelPath.IsValid).ToList(); List<DataModelVisualizationViewModel> toRemoveDynamic = Children.Where(c => c.DataModelPath != null && !c.DataModelPath.IsValid).ToList();

View File

@ -14,16 +14,19 @@
</UserControl.Resources> </UserControl.Resources>
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" />
<ColumnDefinition /> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- Prefix -->
<TextBlock Grid.Column="0" <TextBlock Grid.Column="0"
Text="{Binding PropertyDescription.Prefix}" Text="{Binding PropertyDescription.Prefix}"
Visibility="{Binding PropertyDescription.Prefix, Converter={StaticResource NullToVisibilityConverter}}" Visibility="{Binding PropertyDescription.Prefix, Converter={StaticResource NullToVisibilityConverter}}"
TextAlignment="Right"
Margin="0 0 5 0" /> Margin="0 0 5 0" />
<!-- Value display -->
<!-- Value -->
<TextBlock Grid.Column="1" <TextBlock Grid.Column="1"
Text="{Binding DisplayValue, Mode=OneWay}" Text="{Binding DisplayValue, Mode=OneWay}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
@ -35,6 +38,7 @@
Foreground="{DynamicResource MaterialDesignCheckBoxDisabled}" Foreground="{DynamicResource MaterialDesignCheckBoxDisabled}"
Visibility="{Binding ShowNull, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" /> Visibility="{Binding ShowNull, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
<!-- Affix -->
<TextBlock Grid.Column="2" <TextBlock Grid.Column="2"
Text="{Binding PropertyDescription.Affix}" Text="{Binding PropertyDescription.Affix}"
Visibility="{Binding PropertyDescription.Affix, Converter={StaticResource NullToVisibilityConverter}}" Visibility="{Binding PropertyDescription.Affix, Converter={StaticResource NullToVisibilityConverter}}"

View File

@ -5,6 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:display="clr-namespace:Artemis.UI.DefaultTypes.DataModel.Display" xmlns:display="clr-namespace:Artemis.UI.DefaultTypes.DataModel.Display"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type display:SKColorDataModelDisplayViewModel}}"> d:DataContext="{d:DesignInstance {x:Type display:SKColorDataModelDisplayViewModel}}">
@ -15,25 +16,49 @@
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
<shared:ColorToStringConverter x:Key="SKColorToStringConverter" /> <shared:ColorToStringConverter x:Key="SKColorToStringConverter" />
<shared:SKColorToColorConverter x:Key="SKColorToColorConverter" /> <shared:SKColorToColorConverter x:Key="SKColorToColorConverter" />
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
</ResourceDictionary> </ResourceDictionary>
</UserControl.Resources> </UserControl.Resources>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock x:Name="HexDisplay" <Grid>
Text="{Binding DisplayValue, Converter={StaticResource SKColorToStringConverter}}" <Grid.ColumnDefinitions>
VerticalAlignment="Center" <ColumnDefinition Width="*" />
HorizontalAlignment="Stretch" /> <ColumnDefinition Width="Auto"/>
<Border Width="{Binding ActualHeight, ElementName=HexDisplay}" <ColumnDefinition Width="Auto"/>
Height="{Binding ActualHeight, ElementName=HexDisplay}" </Grid.ColumnDefinitions>
CornerRadius="{Binding ActualHeight, ElementName=HexDisplay}"
Margin="5 0 0 0" <!-- Prefix -->
VerticalAlignment="Center" <TextBlock Grid.Column="0"
HorizontalAlignment="Right" Text="{Binding PropertyDescription.Prefix}"
Background="{StaticResource Checkerboard}"> Visibility="{Binding PropertyDescription.Prefix, Converter={StaticResource NullToVisibilityConverter}}"
<Ellipse Stroke="{DynamicResource NormalBorderBrush}"> TextAlignment="Right"
<Ellipse.Fill> Margin="0 0 5 0" />
<SolidColorBrush Color="{Binding DisplayValue, Converter={StaticResource SKColorToColorConverter}}" />
</Ellipse.Fill> <!-- Value -->
</Ellipse> <StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
</Border> <TextBlock x:Name="HexDisplay"
</StackPanel> Text="{Binding DisplayValue, Converter={StaticResource SKColorToStringConverter}}"
VerticalAlignment="Center"
HorizontalAlignment="Stretch" />
<Border Width="{Binding ActualHeight, ElementName=HexDisplay}"
Height="{Binding ActualHeight, ElementName=HexDisplay}"
CornerRadius="{Binding ActualHeight, ElementName=HexDisplay}"
Margin="5 0 0 0"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Background="{StaticResource Checkerboard}">
<Ellipse Stroke="{DynamicResource NormalBorderBrush}">
<Ellipse.Fill>
<SolidColorBrush Color="{Binding DisplayValue, Converter={StaticResource SKColorToColorConverter}}" />
</Ellipse.Fill>
</Ellipse>
</Border>
</StackPanel>
<!-- Affix -->
<TextBlock Grid.Column="2"
Text="{Binding PropertyDescription.Affix}"
Visibility="{Binding PropertyDescription.Affix, Converter={StaticResource NullToVisibilityConverter}}"
Margin="5 0 0 0" />
</Grid>
</UserControl> </UserControl>