diff --git a/src/Artemis.Core/Extensions/FloatExtensions.cs b/src/Artemis.Core/Extensions/FloatExtensions.cs
index aabe25497..0a4cb9568 100644
--- a/src/Artemis.Core/Extensions/FloatExtensions.cs
+++ b/src/Artemis.Core/Extensions/FloatExtensions.cs
@@ -16,7 +16,7 @@ namespace Artemis.Core
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int RoundToInt(this float number)
{
- return (int) Math.Round(number, MidpointRounding.AwayFromZero);
+ return (int) MathF.Round(number, MidpointRounding.AwayFromZero);
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Extensions/RgbDeviceExtensions.cs b/src/Artemis.Core/Extensions/RgbDeviceExtensions.cs
index 5c1e11353..c6139f30c 100644
--- a/src/Artemis.Core/Extensions/RgbDeviceExtensions.cs
+++ b/src/Artemis.Core/Extensions/RgbDeviceExtensions.cs
@@ -25,11 +25,16 @@ namespace Artemis.Core
public static SKRect ToSKRect(this Rectangle rectangle)
{
return SKRect.Create(
- (float) rectangle.Location.X,
- (float) rectangle.Location.Y,
- (float) rectangle.Size.Width,
- (float) rectangle.Size.Height
+ rectangle.Location.X,
+ rectangle.Location.Y,
+ rectangle.Size.Width,
+ rectangle.Size.Height
);
}
+
+ public static SKRectI ToSKRectI(this Rectangle rectangle)
+ {
+ return SKRectI.Round(ToSKRect(rectangle));
+ }
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs b/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs
index 9f18fa243..06cdab520 100644
--- a/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs
+++ b/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs
@@ -10,6 +10,19 @@ namespace Artemis.Core
///
public class ColorGradient : CorePropertyChanged
{
+ private static readonly SKColor[] FastLedRainbow =
+ {
+ new(0xFFFF0000), // Red
+ new(0xFFFF9900), // Orange
+ new(0xFFFFFF00), // Yellow
+ new(0xFF00FF00), // Green
+ new(0xFF00FF7E), // Aqua
+ new(0xFF0078FF), // Blue
+ new(0xFF9E22FF), // Purple
+ new(0xFFFF34AE), // Pink
+ new(0xFFFF0000) // and back to Red
+ };
+
///
/// Creates a new instance of the class
///
@@ -31,9 +44,9 @@ namespace Artemis.Core
public SKColor[] GetColorsArray(int timesToRepeat = 0)
{
if (timesToRepeat == 0)
- return Stops.OrderBy(c => c.Position).Select(c => c.Color).ToArray();
+ return Stops.Select(c => c.Color).ToArray();
- List colors = Stops.OrderBy(c => c.Position).Select(c => c.Color).ToList();
+ List colors = Stops.Select(c => c.Color).ToList();
List result = new();
for (int i = 0; i <= timesToRepeat; i++)
@@ -53,10 +66,10 @@ namespace Artemis.Core
public float[] GetPositionsArray(int timesToRepeat = 0)
{
if (timesToRepeat == 0)
- return Stops.OrderBy(c => c.Position).Select(c => c.Position).ToArray();
+ return Stops.Select(c => c.Position).ToArray();
// Create stops and a list of divided stops
- List stops = Stops.OrderBy(c => c.Position).Select(c => c.Position / (timesToRepeat + 1)).ToList();
+ List stops = Stops.Select(c => c.Position / (timesToRepeat + 1)).ToList();
List result = new();
// For each repeat cycle, add the base stops to the end result
@@ -74,6 +87,7 @@ namespace Artemis.Core
///
public void OnColorValuesUpdated()
{
+ Stops.Sort((a, b) => a.Position.CompareTo(b.Position));
OnPropertyChanged(nameof(Stops));
}
@@ -86,7 +100,7 @@ namespace Artemis.Core
if (!Stops.Any())
return SKColor.Empty;
- ColorGradientStop[] stops = Stops.OrderBy(x => x.Position).ToArray();
+ ColorGradientStop[] stops = Stops.ToArray();
if (position <= 0) return stops[0].Color;
if (position >= 1) return stops[^1].Color;
ColorGradientStop left = stops[0];
@@ -119,15 +133,14 @@ namespace Artemis.Core
///
public static ColorGradient GetUnicornBarf()
{
- const int amount = 8;
ColorGradient gradient = new();
-
- for (int i = 0; i <= amount; i++)
+ for (int index = 0; index < FastLedRainbow.Length; index++)
{
- float percent = i / (float)amount;
- gradient.Stops.Add(new ColorGradientStop(SKColor.FromHsv(360f * percent, 100, 100), percent));
+ SKColor skColor = FastLedRainbow[index];
+ float position = 1f / (FastLedRainbow.Length - 1f) * index;
+ gradient.Stops.Add(new ColorGradientStop(skColor, position));
}
-
+
return gradient;
}
}
diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs
index 4bfbacc48..6b560ee52 100644
--- a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs
+++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs
@@ -63,7 +63,10 @@ namespace Artemis.Core
///
public Easings.Functions EasingFunction { get; set; }
- internal DataBindingEntity Entity { get; }
+ ///
+ /// Gets the data binding entity this data binding uses for persistent storage
+ ///
+ public DataBindingEntity Entity { get; }
///
/// Gets the current value of the data binding
@@ -100,6 +103,25 @@ namespace Artemis.Core
return Registration?.Getter.Method.ReturnType;
}
+ ///
+ /// Releases the unmanaged resources used by the object and optionally releases the managed resources.
+ ///
+ ///
+ /// to release both managed and unmanaged resources;
+ /// to release only unmanaged resources.
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _disposed = true;
+
+ if (Registration != null)
+ Registration.DataBinding = null;
+ DataBindingMode?.Dispose();
+ }
+ }
+
private void ResetEasing(TProperty value)
{
_previousValue = GetInterpolatedValue();
@@ -192,27 +214,6 @@ namespace Artemis.Core
_reapplyValue = true;
}
- #region IDisposable
-
- ///
- /// Releases the unmanaged resources used by the object and optionally releases the managed resources.
- ///
- ///
- /// to release both managed and unmanaged resources;
- /// to release only unmanaged resources.
- ///
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- {
- _disposed = true;
-
- if (Registration != null)
- Registration.DataBinding = null;
- DataBindingMode?.Dispose();
- }
- }
-
///
public void Dispose()
{
@@ -220,8 +221,6 @@ namespace Artemis.Core
GC.SuppressFinalize(this);
}
- #endregion
-
#region Mode management
///
@@ -245,6 +244,16 @@ namespace Artemis.Core
ApplyDataBindingMode();
}
+ ///
+ /// Replaces the current data binding mode with one based on the provided data binding mode entity
+ ///
+ /// The data binding mode entity to base the new data binding mode upon
+ public void ApplyDataBindingEntity(IDataBindingModeEntity dataBindingModeEntity)
+ {
+ Entity.DataBindingMode = dataBindingModeEntity;
+ ApplyDataBindingMode();
+ }
+
private void ApplyDataBindingMode()
{
DataBindingMode?.Dispose();
diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelEvent.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelEvent.cs
index aeca78a60..cd56a38b2 100644
--- a/src/Artemis.Core/Models/Profile/DataModel/DataModelEvent.cs
+++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelEvent.cs
@@ -29,9 +29,13 @@ namespace Artemis.Core
}
///
- [DataModelProperty(Name = "Last event trigger", Description = "The time at which the event last triggered")]
+ [DataModelProperty(Name = "Last trigger", Description = "The time at which the event last triggered")]
public DateTime LastTrigger { get; private set; }
+ ///
+ [DataModelProperty(Name = "Time since trigger", Description = "The time that has passed since the last trigger")]
+ public TimeSpan TimeSinceLastTrigger => DateTime.Now - LastTrigger;
+
///
/// Gets the event arguments of the last time the event was triggered
///
@@ -85,6 +89,7 @@ namespace Artemis.Core
public Type ArgumentsType => typeof(T);
///
+ [DataModelIgnore]
public string TriggerPastParticiple => "triggered";
///
@@ -147,14 +152,18 @@ namespace Artemis.Core
}
///
- [DataModelProperty(Name = "Last event trigger", Description = "The time at which the event last triggered")]
+ [DataModelProperty(Name = "Last trigger", Description = "The time at which the event last triggered")]
public DateTime LastTrigger { get; private set; }
+ ///
+ [DataModelProperty(Name = "Time since trigger", Description = "The time that has passed since the last trigger")]
+ public TimeSpan TimeSinceLastTrigger => DateTime.Now - LastTrigger;
+
///
/// Gets the event arguments of the last time the event was triggered
///
[DataModelProperty(Description = "The arguments of the last time this event triggered")]
- public DataModelEventArgs? LastEventArguments { get; private set; }
+ public DataModelEventArgs? LastTriggerArguments { get; private set; }
///
[DataModelProperty(Description = "The total amount of times this event has triggered since the module was activated")]
@@ -174,7 +183,7 @@ namespace Artemis.Core
{
DataModelEventArgs eventArgs = new() {TriggerTime = DateTime.Now};
- LastEventArguments = eventArgs;
+ LastTriggerArguments = eventArgs;
LastTrigger = DateTime.Now;
TriggerCount++;
@@ -201,6 +210,7 @@ namespace Artemis.Core
public Type ArgumentsType => typeof(DataModelEventArgs);
///
+ [DataModelIgnore]
public string TriggerPastParticiple => "triggered";
///
@@ -217,7 +227,7 @@ namespace Artemis.Core
///
[DataModelIgnore]
- public DataModelEventArgs? LastEventArgumentsUntyped => LastEventArguments;
+ public DataModelEventArgs? LastEventArgumentsUntyped => LastTriggerArguments;
///
[DataModelIgnore]
diff --git a/src/Artemis.Core/Models/Profile/DataModel/IDataModelEvent.cs b/src/Artemis.Core/Models/Profile/DataModel/IDataModelEvent.cs
index 954c2b7a6..4357dc3c6 100644
--- a/src/Artemis.Core/Models/Profile/DataModel/IDataModelEvent.cs
+++ b/src/Artemis.Core/Models/Profile/DataModel/IDataModelEvent.cs
@@ -13,6 +13,11 @@ namespace Artemis.Core
///
DateTime LastTrigger { get; }
+ ///
+ /// Gets the time that has passed since the last trigger
+ ///
+ TimeSpan TimeSinceLastTrigger { get; }
+
///
/// Gets the amount of times the event was triggered
///
diff --git a/src/Artemis.Core/Models/Profile/DataModel/ValueChangedEvent/DataModelValueChangedEvent.cs b/src/Artemis.Core/Models/Profile/DataModel/ValueChangedEvent/DataModelValueChangedEvent.cs
index 3a2ee7de8..fcf52f32b 100644
--- a/src/Artemis.Core/Models/Profile/DataModel/ValueChangedEvent/DataModelValueChangedEvent.cs
+++ b/src/Artemis.Core/Models/Profile/DataModel/ValueChangedEvent/DataModelValueChangedEvent.cs
@@ -14,6 +14,7 @@ namespace Artemis.Core
public T? LastValue { get; private set; }
public T? CurrentValue { get; private set; }
public DateTime LastTrigger { get; private set; }
+ public TimeSpan TimeSinceLastTrigger => DateTime.Now - LastTrigger;
public int TriggerCount { get; private set; }
public Type ArgumentsType { get; } = typeof(DataModelValueChangedEventArgs);
public string TriggerPastParticiple => "changed";
diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs
index 1d273c71b..9557e3771 100644
--- a/src/Artemis.Core/Models/Profile/Folder.cs
+++ b/src/Artemis.Core/Models/Profile/Folder.cs
@@ -4,7 +4,6 @@ using System.Linq;
using Artemis.Core.LayerEffects;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Abstract;
-using Newtonsoft.Json;
using SkiaSharp;
namespace Artemis.Core
@@ -153,8 +152,10 @@ namespace Artemis.Core
SKPath path = new() {FillType = SKPathFillType.Winding};
foreach (ProfileElement child in Children)
+ {
if (child is RenderProfileElement effectChild && effectChild.Path != null)
path.AddPath(effectChild.Path);
+ }
Path = path;
@@ -168,7 +169,7 @@ namespace Artemis.Core
#region Rendering
///
- public override void Render(SKCanvas canvas, SKPoint basePosition)
+ public override void Render(SKCanvas canvas, SKPointI basePosition)
{
if (Disposed)
throw new ObjectDisposedException("Folder");
@@ -192,12 +193,12 @@ namespace Artemis.Core
SKPaint layerPaint = new();
try
{
- SKRect rendererBounds = SKRect.Create(0, 0, Path.Bounds.Width, Path.Bounds.Height);
+ SKRectI rendererBounds = SKRectI.Create(0, 0, Bounds.Width, Bounds.Height);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
baseLayerEffect.PreProcess(canvas, rendererBounds, layerPaint);
canvas.SaveLayer(layerPaint);
- canvas.Translate(Path.Bounds.Left - basePosition.X, Path.Bounds.Top - basePosition.Y);
+ canvas.Translate(Bounds.Left - basePosition.X, Bounds.Top - basePosition.Y);
// If required, apply the opacity override of the module to the root folder
if (IsRootFolder && Profile.Module.OpacityOverride < 1)
@@ -212,7 +213,7 @@ namespace Artemis.Core
// Iterate the children in reverse because the first layer must be rendered last to end up on top
for (int index = Children.Count - 1; index > -1; index--)
- Children[index].Render(canvas, new SKPoint(Path.Bounds.Left, Path.Bounds.Top));
+ Children[index].Render(canvas, new SKPointI(Bounds.Left, Bounds.Top));
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
baseLayerEffect.PostProcess(canvas, rendererBounds, layerPaint);
diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs
index 3feac187a..47a08ab8a 100644
--- a/src/Artemis.Core/Models/Profile/Layer.cs
+++ b/src/Artemis.Core/Models/Profile/Layer.cs
@@ -277,7 +277,7 @@ namespace Artemis.Core
}
///
- public override void Render(SKCanvas canvas, SKPoint basePosition)
+ public override void Render(SKCanvas canvas, SKPointI basePosition)
{
if (Disposed)
throw new ObjectDisposedException("Layer");
@@ -312,7 +312,7 @@ namespace Artemis.Core
}
}
- private void RenderTimeline(Timeline timeline, SKCanvas canvas, SKPoint basePosition)
+ private void RenderTimeline(Timeline timeline, SKCanvas canvas, SKPointI basePosition)
{
if (Path == null || LayerBrush == null)
throw new ArtemisCoreException("The layer is not yet ready for rendering");
@@ -329,12 +329,11 @@ namespace Artemis.Core
try
{
canvas.Save();
- canvas.Translate(Path.Bounds.Left - basePosition.X, Path.Bounds.Top - basePosition.Y);
+ canvas.Translate(Bounds.Left - basePosition.X, Bounds.Top - basePosition.Y);
using SKPath clipPath = new(Path);
- clipPath.Transform(SKMatrix.CreateTranslation(Path.Bounds.Left * -1, Path.Bounds.Top * -1));
- canvas.ClipPath(clipPath);
-
- SKRect layerBounds = SKRect.Create(0, 0, Path.Bounds.Width, Path.Bounds.Height);
+ clipPath.Transform(SKMatrix.CreateTranslation(Bounds.Left * -1, Bounds.Top * -1));
+ canvas.ClipPath(clipPath, SKClipOperation.Intersect, true);
+ SKRectI layerBounds = SKRectI.Create(0, 0, Bounds.Width, Bounds.Height);
// Apply blend mode and color
layerPaint.BlendMode = General.BlendMode.CurrentValue;
@@ -435,7 +434,7 @@ namespace Artemis.Core
OnRenderPropertiesUpdated();
}
- internal SKPoint GetLayerAnchorPosition(SKPath layerPath, bool applyTranslation, bool zeroBased)
+ internal SKPoint GetLayerAnchorPosition(bool applyTranslation, bool zeroBased)
{
if (Disposed)
throw new ObjectDisposedException("Layer");
@@ -444,14 +443,14 @@ namespace Artemis.Core
// Start at the center of the shape
SKPoint position = zeroBased
- ? new SKPoint(layerPath.Bounds.MidX - layerPath.Bounds.Left, layerPath.Bounds.MidY - layerPath.Bounds.Top)
- : new SKPoint(layerPath.Bounds.MidX, layerPath.Bounds.MidY);
+ ? new SKPointI(Bounds.MidX - Bounds.Left, Bounds.MidY - Bounds.Top)
+ : new SKPointI(Bounds.MidX, Bounds.MidY);
// Apply translation
if (applyTranslation)
{
- position.X += positionProperty.X * layerPath.Bounds.Width;
- position.Y += positionProperty.Y * layerPath.Bounds.Height;
+ position.X += positionProperty.X * Bounds.Width;
+ position.Y += positionProperty.Y * Bounds.Height;
}
return position;
@@ -479,7 +478,7 @@ namespace Artemis.Core
SKSize sizeProperty = Transform.Scale.CurrentValue;
float rotationProperty = Transform.Rotation.CurrentValue;
- SKPoint anchorPosition = GetLayerAnchorPosition(Path, true, zeroBased);
+ SKPoint anchorPosition = GetLayerAnchorPosition(true, zeroBased);
SKPoint anchorProperty = Transform.AnchorPoint.CurrentValue;
// Translation originates from the unscaled center of the shape and is tied to the anchor
diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs
index 96616f104..8f3cf81a9 100644
--- a/src/Artemis.Core/Models/Profile/Profile.cs
+++ b/src/Artemis.Core/Models/Profile/Profile.cs
@@ -81,7 +81,7 @@ namespace Artemis.Core
}
///
- public override void Render(SKCanvas canvas, SKPoint basePosition)
+ public override void Render(SKCanvas canvas, SKPointI basePosition)
{
lock (_lock)
{
diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs
index 1072e4a94..a88d13f8e 100644
--- a/src/Artemis.Core/Models/Profile/ProfileElement.cs
+++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs
@@ -104,7 +104,7 @@ namespace Artemis.Core
///
/// Renders the element
///
- public abstract void Render(SKCanvas canvas, SKPoint basePosition);
+ public abstract void Render(SKCanvas canvas, SKPointI basePosition);
///
/// Resets the internal state of the element
diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs
index 899a01ff9..c2c967c54 100644
--- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs
+++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs
@@ -16,6 +16,9 @@ namespace Artemis.Core
///
public abstract class RenderProfileElement : ProfileElement
{
+ private SKPath? _path;
+ private SKRectI _bounds;
+
internal RenderProfileElement(Profile profile) : base(profile)
{
Timeline = new Timeline();
@@ -113,7 +116,6 @@ namespace Artemis.Core
#region Properties
- private SKPath? _path;
internal abstract RenderElementEntity RenderElementEntity { get; }
///
@@ -141,14 +143,14 @@ namespace Artemis.Core
SetAndNotify(ref _path, value);
// I can't really be sure about the performance impact of calling Bounds often but
// SkiaSharp calls SkiaApi.sk_path_get_bounds (Handle, &rect); which sounds expensive
- Bounds = value?.Bounds ?? SKRect.Empty;
+ Bounds = SKRectI.Round(value?.Bounds ?? SKRect.Empty);
}
}
///
/// The bounds of this entity
///
- public SKRect Bounds
+ public SKRectI Bounds
{
get => _bounds;
private set => SetAndNotify(ref _bounds, value);
@@ -158,8 +160,7 @@ namespace Artemis.Core
#region Property group expansion
internal List ExpandedPropertyGroups;
- private SKRect _bounds;
-
+
///
/// Determines whether the provided property group is expanded
///
diff --git a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs
index cee4d8dfd..d28885e65 100644
--- a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs
+++ b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs
@@ -337,6 +337,7 @@ namespace Artemis.Core
Layout = layout;
Layout.ApplyDevice(this);
+ CalculateRenderProperties();
OnDeviceUpdated();
}
diff --git a/src/Artemis.Core/Models/Surface/ArtemisLed.cs b/src/Artemis.Core/Models/Surface/ArtemisLed.cs
index b8b81ec5b..cec97bd94 100644
--- a/src/Artemis.Core/Models/Surface/ArtemisLed.cs
+++ b/src/Artemis.Core/Models/Surface/ArtemisLed.cs
@@ -8,8 +8,8 @@ namespace Artemis.Core
///
public class ArtemisLed : CorePropertyChanged
{
- private SKRect _absoluteRectangle;
- private SKRect _rectangle;
+ private SKRectI _absoluteRectangle;
+ private SKRectI _rectangle;
internal ArtemisLed(Led led, ArtemisDevice device)
{
@@ -31,7 +31,7 @@ namespace Artemis.Core
///
/// Gets the rectangle covering the LED positioned relative to the
///
- public SKRect Rectangle
+ public SKRectI Rectangle
{
get => _rectangle;
private set => SetAndNotify(ref _rectangle, value);
@@ -40,7 +40,7 @@ namespace Artemis.Core
///
/// Gets the rectangle covering the LED
///
- public SKRect AbsoluteRectangle
+ public SKRectI AbsoluteRectangle
{
get => _absoluteRectangle;
private set => SetAndNotify(ref _absoluteRectangle, value);
@@ -59,8 +59,8 @@ namespace Artemis.Core
internal void CalculateRectangles()
{
- Rectangle = RgbLed.Boundary.ToSKRect();
- AbsoluteRectangle = RgbLed.AbsoluteBoundary.ToSKRect();
+ Rectangle = RgbLed.Boundary.ToSKRectI();
+ AbsoluteRectangle = RgbLed.AbsoluteBoundary.ToSKRectI();
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs
index fb1a8ccad..b2f6536ba 100644
--- a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs
+++ b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs
@@ -177,7 +177,7 @@ namespace Artemis.Core.Modules
lock (_lock)
{
// Render the profile
- ActiveProfile?.Render(canvas, SKPoint.Empty);
+ ActiveProfile?.Render(canvas, SKPointI.Empty);
}
ProfileRendered(deltaTime, canvas, canvasInfo);
diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs
index 3ab1a99ef..0b4379a09 100644
--- a/src/Artemis.Core/Plugins/Plugin.cs
+++ b/src/Artemis.Core/Plugins/Plugin.cs
@@ -107,7 +107,7 @@ namespace Artemis.Core
/// If found, the instance of the feature
public T? GetFeature() where T : PluginFeature
{
- return _features.FirstOrDefault(i => i.Instance is T) as T;
+ return _features.FirstOrDefault(i => i.Instance is T)?.Instance as T;
}
///
@@ -164,6 +164,11 @@ namespace Artemis.Core
}
}
+ internal bool HasEnabledFeatures()
+ {
+ return Entity.Features.Any(f => f.IsEnabled) || Features.Any(f => f.AlwaysEnabled);
+ }
+
#region IDisposable
///
diff --git a/src/Artemis.Core/Plugins/PluginFeatureAttribute.cs b/src/Artemis.Core/Plugins/PluginFeatureAttribute.cs
index 0bb78fb75..40863ce25 100644
--- a/src/Artemis.Core/Plugins/PluginFeatureAttribute.cs
+++ b/src/Artemis.Core/Plugins/PluginFeatureAttribute.cs
@@ -23,5 +23,10 @@ namespace Artemis.Core
/// available icons
///
public string? Icon { get; set; }
+
+ ///
+ /// Marks the feature to always be enabled as long as the plugin is enabled
+ ///
+ public bool AlwaysEnabled { get; set; }
}
-}
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/PluginFeatureInfo.cs b/src/Artemis.Core/Plugins/PluginFeatureInfo.cs
index b8515197f..47f34cbaa 100644
--- a/src/Artemis.Core/Plugins/PluginFeatureInfo.cs
+++ b/src/Artemis.Core/Plugins/PluginFeatureInfo.cs
@@ -28,7 +28,8 @@ namespace Artemis.Core
Name = attribute?.Name ?? featureType.Name.Humanize(LetterCasing.Title);
Description = attribute?.Description;
Icon = attribute?.Icon;
-
+ AlwaysEnabled = attribute?.AlwaysEnabled ?? false;
+
if (Icon != null) return;
if (typeof(BaseDataModelExpansion).IsAssignableFrom(featureType))
Icon = "TableAdd";
@@ -55,6 +56,7 @@ namespace Artemis.Core
Name = attribute?.Name ?? instance.GetType().Name.Humanize(LetterCasing.Title);
Description = attribute?.Description;
Icon = attribute?.Icon;
+ AlwaysEnabled = attribute?.AlwaysEnabled ?? false;
Instance = instance;
if (Icon != null) return;
@@ -111,6 +113,12 @@ namespace Artemis.Core
set => SetAndNotify(ref _icon, value);
}
+ ///
+ /// Marks the feature to always be enabled as long as the plugin is enabled and cannot be disabled
+ ///
+ [JsonProperty]
+ public bool AlwaysEnabled { get; }
+
///
/// Gets the feature this info is associated with
///
diff --git a/src/Artemis.Core/RGB.NET/SKTexture.cs b/src/Artemis.Core/RGB.NET/SKTexture.cs
index 77a540282..27a9f6d5b 100644
--- a/src/Artemis.Core/RGB.NET/SKTexture.cs
+++ b/src/Artemis.Core/RGB.NET/SKTexture.cs
@@ -1,4 +1,5 @@
using System;
+using System.Buffers;
using System.Runtime.InteropServices;
using Artemis.Core.SkiaSharp;
using RGB.NET.Core;
@@ -12,25 +13,54 @@ namespace Artemis.Core
///
public sealed class SKTexture : PixelTexture, IDisposable
{
+ private readonly bool _isScaledDown;
private readonly SKPixmap _pixelData;
private readonly IntPtr _pixelDataPtr;
#region Constructors
- internal SKTexture(IManagedGraphicsContext? graphicsContext, int width, int height, float scale) : base(width, height, 4, new AverageByteSampler())
+ internal SKTexture(IManagedGraphicsContext? graphicsContext, int width, int height, float scale) : base(width, height, DATA_PER_PIXEL, new AverageByteSampler())
{
ImageInfo = new SKImageInfo(width, height);
- Surface = graphicsContext == null
- ? SKSurface.Create(ImageInfo)
+ Surface = graphicsContext == null
+ ? SKSurface.Create(ImageInfo)
: SKSurface.Create(graphicsContext.GraphicsContext, true, ImageInfo);
RenderScale = scale;
-
+ _isScaledDown = Math.Abs(scale - 1) > 0.001;
_pixelDataPtr = Marshal.AllocHGlobal(ImageInfo.BytesSize);
_pixelData = new SKPixmap(ImageInfo, _pixelDataPtr, ImageInfo.RowBytes);
}
#endregion
+ private void ReleaseUnmanagedResources()
+ {
+ Marshal.FreeHGlobal(_pixelDataPtr);
+ }
+
+ ///
+ ~SKTexture()
+ {
+ ReleaseUnmanagedResources();
+ }
+
+ ///
+ public void Dispose()
+ {
+ Surface.Dispose();
+ _pixelData.Dispose();
+
+ ReleaseUnmanagedResources();
+ GC.SuppressFinalize(this);
+ }
+
+ #region Constants
+
+ private const int STACK_ALLOC_LIMIT = 1024;
+ private const int DATA_PER_PIXEL = 4;
+
+ #endregion
+
#region Methods
///
@@ -53,6 +83,61 @@ namespace Artemis.Core
return new(pixel[2], pixel[1], pixel[0]);
}
+ ///
+ public override Color this[in Rectangle rectangle]
+ {
+ get
+ {
+ if (Data.Length == 0) return Color.Transparent;
+
+ SKRectI skRectI = CreatedFlooredRectI(
+ Size.Width * rectangle.Location.X.Clamp(0, 1),
+ Size.Height * rectangle.Location.Y.Clamp(0, 1),
+ Size.Width * rectangle.Size.Width.Clamp(0, 1),
+ Size.Height * rectangle.Size.Height.Clamp(0, 1)
+ );
+
+ if (skRectI.Width == 0 || skRectI.Height == 0) return Color.Transparent;
+ if (skRectI.Width == 1 && skRectI.Height == 1) return GetColor(GetPixelData(skRectI.Left, skRectI.Top));
+
+ int bufferSize = skRectI.Width * skRectI.Height * DATA_PER_PIXEL;
+ if (bufferSize <= STACK_ALLOC_LIMIT)
+ {
+ Span buffer = stackalloc byte[bufferSize];
+ GetRegionData(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, buffer);
+
+ Span pixelData = stackalloc byte[DATA_PER_PIXEL];
+ Sampler.SampleColor(new SamplerInfo(skRectI.Width, skRectI.Height, buffer), pixelData);
+
+ return GetColor(pixelData);
+ }
+ else
+ {
+ byte[] rent = ArrayPool.Shared.Rent(bufferSize);
+
+ Span buffer = new Span(rent).Slice(0, bufferSize);
+ GetRegionData(skRectI.Left, skRectI.Top, skRectI.Width, skRectI.Height, buffer);
+
+ Span pixelData = stackalloc byte[DATA_PER_PIXEL];
+ Sampler.SampleColor(new SamplerInfo(skRectI.Width, skRectI.Height, buffer), pixelData);
+
+ ArrayPool.Shared.Return(rent);
+
+ return GetColor(pixelData);
+ }
+ }
+ }
+
+ private static SKRectI CreatedFlooredRectI(float x, float y, float width, float height)
+ {
+ return new(
+ width <= 0.0 ? checked((int) Math.Floor(x)) : checked((int) Math.Ceiling(x)),
+ height <= 0.0 ? checked((int) Math.Floor(y)) : checked((int) Math.Ceiling(y)),
+ width >= 0.0 ? checked((int) Math.Floor(x + width)) : checked((int) Math.Ceiling(x + width)),
+ height >= 0.0 ? checked((int) Math.Floor(y + height)) : checked((int) Math.Ceiling(y + height))
+ );
+ }
+
#endregion
#region Properties & Fields
@@ -84,30 +169,5 @@ namespace Artemis.Core
public bool IsInvalid { get; private set; }
#endregion
-
- #region IDisposable
-
- private void ReleaseUnmanagedResources()
- {
- Marshal.FreeHGlobal(_pixelDataPtr);
- }
-
- ///
- public void Dispose()
- {
- Surface.Dispose();
- _pixelData.Dispose();
-
- ReleaseUnmanagedResources();
- GC.SuppressFinalize(this);
- }
-
- ///
- ~SKTexture()
- {
- ReleaseUnmanagedResources();
- }
-
- #endregion
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs
index 17961a9e2..1a0f26a4a 100644
--- a/src/Artemis.Core/Services/CoreService.cs
+++ b/src/Artemis.Core/Services/CoreService.cs
@@ -144,16 +144,17 @@ namespace Artemis.Core.Services
SKCanvas canvas = texture.Surface.Canvas;
canvas.Save();
- canvas.Scale(texture.RenderScale);
+ if (Math.Abs(texture.RenderScale - 1) > 0.001)
+ 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.ImageInfo);
}
-
+
OnFrameRendering(new FrameRenderingEventArgs(canvas, args.DeltaTime, _rgbService.Surface));
canvas.RestoreToCount(-1);
canvas.Flush();
diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs
index cadb77caf..7840660ac 100644
--- a/src/Artemis.Core/Services/PluginManagementService.cs
+++ b/src/Artemis.Core/Services/PluginManagementService.cs
@@ -207,7 +207,7 @@ namespace Artemis.Core.Services
// ReSharper disable InconsistentlySynchronizedField - It's read-only, idc
_logger.Debug("Loaded {count} plugin(s)", _plugins.Count);
- bool adminRequired = _plugins.Any(p => p.Info.RequiresAdmin && p.Entity.IsEnabled && p.Entity.Features.Any(f => f.IsEnabled));
+ bool adminRequired = _plugins.Any(p => p.Info.RequiresAdmin && p.Entity.IsEnabled && p.HasEnabledFeatures());
if (!isElevated && adminRequired)
{
_logger.Information("Restarting because one or more plugins requires elevation");
@@ -340,7 +340,7 @@ namespace Artemis.Core.Services
if (plugin.Assembly == null)
throw new ArtemisPluginException(plugin, "Cannot enable a plugin that hasn't successfully been loaded");
- if (plugin.Info.RequiresAdmin && plugin.Entity.Features.Any(f => f.IsEnabled) && !_isElevated)
+ if (plugin.Info.RequiresAdmin && plugin.HasEnabledFeatures() && !_isElevated)
{
if (!saveState)
throw new ArtemisCoreException("Cannot enable a plugin that requires elevation without saving it's state.");
@@ -387,7 +387,7 @@ namespace Artemis.Core.Services
}
// Activate features after they are all loaded
- foreach (PluginFeatureInfo pluginFeature in plugin.Features.Where(f => f.Instance != null && f.Instance.Entity.IsEnabled))
+ foreach (PluginFeatureInfo pluginFeature in plugin.Features.Where(f => f.Instance != null && (f.Instance.Entity.IsEnabled || f.AlwaysEnabled)))
{
try
{
diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs
index 40b502208..4f812c886 100644
--- a/src/Artemis.Core/Services/RgbService.cs
+++ b/src/Artemis.Core/Services/RgbService.cs
@@ -258,9 +258,17 @@ namespace Artemis.Core.Services
else
_logger.Debug("Creating SKTexture with software-based graphics context");
+ float evenWidth = Surface.Boundary.Size.Width;
+ if (evenWidth % 2 != 0)
+ evenWidth++;
+ float evenHeight = Surface.Boundary.Size.Height;
+ if (evenHeight % 2 != 0)
+ evenHeight++;
+
float renderScale = (float) _renderScaleSetting.Value;
- int width = Math.Max(1, MathF.Min(Surface.Boundary.Size.Width * renderScale, 4096).RoundToInt());
- int height = Math.Max(1, MathF.Min(Surface.Boundary.Size.Height * renderScale, 4096).RoundToInt());
+ int width = Math.Max(1, MathF.Min(evenWidth * renderScale, 4096).RoundToInt());
+ int height = Math.Max(1, MathF.Min(evenHeight * renderScale, 4096).RoundToInt());
+
_texture?.Dispose();
_texture = new SKTexture(graphicsContext, width, height, renderScale);
_textureBrush.Texture = _texture;
diff --git a/src/Artemis.Core/Utilities/IntroAnimation.cs b/src/Artemis.Core/Utilities/IntroAnimation.cs
index 06cd3d765..9bb5456e4 100644
--- a/src/Artemis.Core/Utilities/IntroAnimation.cs
+++ b/src/Artemis.Core/Utilities/IntroAnimation.cs
@@ -30,7 +30,7 @@ namespace Artemis.Core
public void Render(double deltaTime, SKCanvas canvas)
{
AnimationProfile.Update(deltaTime);
- AnimationProfile.Render(canvas, SKPoint.Empty);
+ AnimationProfile.Render(canvas, SKPointI.Empty);
}
private Profile CreateIntroProfile()
diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs
index ffb154dac..bc4b3648f 100644
--- a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs
+++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs
@@ -6,9 +6,11 @@ using System.Linq;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
+using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Artemis.Core;
+using SkiaSharp;
using Stylet;
namespace Artemis.UI.Shared
@@ -54,6 +56,7 @@ namespace Artemis.UI.Shared
_timer = new Timer(40);
_timer.Elapsed += TimerOnTick;
+ MouseLeftButtonUp += OnMouseLeftButtonUp;
Loaded += OnLoaded;
Unloaded += OnUnloaded;
}
@@ -85,18 +88,6 @@ namespace Artemis.UI.Shared
set => SetValue(HighlightedLedsProperty, value);
}
- ///
- /// Releases the unmanaged resources used by the object and optionally releases the managed resources.
- ///
- ///
- /// to release both managed and unmanaged resources;
- /// to release only unmanaged resources.
- ///
- protected virtual void Dispose(bool disposing)
- {
- if (disposing) _timer.Stop();
- }
-
///
protected override void OnRender(DrawingContext drawingContext)
{
@@ -149,6 +140,7 @@ namespace Artemis.UI.Shared
return ResizeKeepAspect(deviceSize, availableSize.Width, availableSize.Height);
}
+
private static Size ResizeKeepAspect(Size src, double maxWidth, double maxHeight)
{
double scale;
@@ -191,6 +183,21 @@ namespace Artemis.UI.Shared
}
}
+ private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
+ {
+ if (Device == null)
+ return;
+
+ Point position = e.GetPosition(this);
+ double x = (position.X / RenderSize.Width);
+ double y = (position.Y / RenderSize.Height);
+
+ Point scaledPosition = new(x * Device.Rectangle.Width, y * Device.Rectangle.Height);
+ DeviceVisualizerLed? deviceVisualizerLed = _deviceVisualizerLeds.FirstOrDefault(l => l.DisplayGeometry != null && l.LedRect.Contains(scaledPosition));
+ if (deviceVisualizerLed != null)
+ OnLedClicked(new LedClickedEventArgs(deviceVisualizerLed.Led.Device, deviceVisualizerLed.Led));
+ }
+
private void OnLoaded(object? sender, RoutedEventArgs e)
{
_timer.Start();
@@ -310,11 +317,43 @@ namespace Artemis.UI.Shared
drawingContext.Close();
}
+ #region Events
+
+ public event EventHandler? LedClicked;
+
+ ///
+ /// Invokes the event
+ ///
+ ///
+ protected virtual void OnLedClicked(LedClickedEventArgs e)
+ {
+ LedClicked?.Invoke(this, e);
+ }
+
+ #endregion
+
+ #region IDisposable
+
+ ///
+ /// Releases the unmanaged resources used by the object and optionally releases the managed resources.
+ ///
+ ///
+ /// to release both managed and unmanaged resources;
+ /// to release only unmanaged resources.
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing) _timer.Stop();
+ }
+
+
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Events/LedClickedEventArgs.cs b/src/Artemis.UI.Shared/Events/LedClickedEventArgs.cs
new file mode 100644
index 000000000..3455b9800
--- /dev/null
+++ b/src/Artemis.UI.Shared/Events/LedClickedEventArgs.cs
@@ -0,0 +1,27 @@
+using System;
+using Artemis.Core;
+
+namespace Artemis.UI.Shared
+{
+ ///
+ /// Provides data on LED click events raised by the device visualizer
+ ///
+ public class LedClickedEventArgs : EventArgs
+ {
+ internal LedClickedEventArgs(ArtemisDevice device, ArtemisLed led)
+ {
+ Device = device;
+ Led = led;
+ }
+
+ ///
+ /// The device that was clicked
+ ///
+ public ArtemisDevice Device { get; set; }
+
+ ///
+ /// The LED that was clicked
+ ///
+ public ArtemisLed Led { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs
index 720bf0ad3..79d71359b 100644
--- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs
+++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs
@@ -11,6 +11,7 @@ using Artemis.UI.Shared.Services.Models;
using Ninject;
using Ninject.Parameters;
using Serilog;
+using SkiaSharp;
using SkiaSharp.Views.WPF;
using Stylet;
@@ -350,7 +351,10 @@ namespace Artemis.UI.Shared.Services
public List GetLedsInRectangle(Rect rect)
{
- return _rgbService.EnabledDevices.SelectMany(d => d.Leds).Where(led => led.AbsoluteRectangle.IntersectsWith(rect.ToSKRect())).ToList();
+ return _rgbService.EnabledDevices
+ .SelectMany(d => d.Leds)
+ .Where(led => led.AbsoluteRectangle.IntersectsWith(SKRectI.Round(rect.ToSKRect())))
+ .ToList();
}
#region Copy/paste
diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.xaml b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.xaml
index 65e27826f..f3d4a2b4a 100644
--- a/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.xaml
+++ b/src/Artemis.UI/DefaultTypes/PropertyInput/FloatPropertyInputView.xaml
@@ -6,6 +6,7 @@
xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
+ xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance propertyInput:FloatPropertyInputViewModel}">
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupView.xaml b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupView.xaml
index 421cf840d..7afa0ef9a 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupView.xaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupView.xaml
@@ -154,7 +154,7 @@
-
+
diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs
index 437fe3252..de5822a2c 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionGroupViewModel.cs
@@ -1,6 +1,6 @@
using System;
+using System.Collections.Generic;
using System.Linq;
-using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Extensions;
using Artemis.UI.Ninject.Factories;
@@ -16,7 +16,6 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
private readonly IDataModelConditionsVmFactory _dataModelConditionsVmFactory;
private readonly IProfileEditorService _profileEditorService;
private bool _isEventGroup;
- private bool _isInitialized;
private bool _isRootGroup;
public DataModelConditionGroupViewModel(DataModelConditionGroup dataModelConditionGroup,
@@ -30,12 +29,6 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
_dataModelConditionsVmFactory = dataModelConditionsVmFactory;
Items.CollectionChanged += (_, _) => NotifyOfPropertyChange(nameof(DisplayBooleanOperator));
-
- Execute.PostToUIThread(async () =>
- {
- await Task.Delay(50);
- IsInitialized = true;
- });
}
public ConditionGroupType GroupType { get; }
@@ -63,12 +56,6 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
}
}
- public bool IsInitialized
- {
- get => _isInitialized;
- set => SetAndNotify(ref _isInitialized, value);
- }
-
public bool DisplayBooleanOperator => Items.Count > 1;
public bool DisplayEvaluationResult => GroupType == ConditionGroupType.General && !IsEventGroup;
public string SelectedBooleanOperator => DataModelConditionGroup.BooleanOperator.Humanize();
@@ -132,7 +119,9 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
{
NotifyOfPropertyChange(nameof(SelectedBooleanOperator));
// Remove VMs of effects no longer applied on the layer
- Items.RemoveRange(Items.Where(c => !DataModelConditionGroup.Children.Contains(c.Model)).ToList());
+ List toRemove = Items.Where(c => !DataModelConditionGroup.Children.Contains(c.Model)).ToList();
+ if (toRemove.Any())
+ Items.RemoveRange(toRemove);
foreach (DataModelConditionPart childModel in Model.Children)
{
@@ -169,8 +158,10 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
IsEventGroup = Items.Any(i => i is DataModelConditionEventViewModel);
if (IsEventGroup)
+ {
if (DataModelConditionGroup.BooleanOperator != BooleanOperator.And)
SelectBooleanOperator("And");
+ }
OnUpdated();
}
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeView.xaml
index 493b65c76..7fded19b2 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeView.xaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeView.xaml
@@ -42,12 +42,12 @@
Margin="-10 0 -10 -5">
-
+
-
+
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs
index b45c7728b..f7cfd0071 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs
@@ -44,6 +44,11 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.Conditio
_profileEditorService.UpdateSelectedProfileElement();
}
+ public void RemoveCondition(DataBindingCondition dataBindingCondition)
+ {
+ ConditionalDataBinding.RemoveCondition(dataBindingCondition);
+ }
+
protected override void OnInitialActivate()
{
Initialize();
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionView.xaml
index f3e1833c6..6ae8e1eeb 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionView.xaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionView.xaml
@@ -4,24 +4,63 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:s="https://github.com/canton7/Stylet"
+ xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionViewModel.cs
index 81d9dfe6a..4361be98d 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/DataBindingConditionViewModel.cs
@@ -61,6 +61,17 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.Conditio
ActiveItem?.Evaluate();
}
+ public void AddCondition()
+ {
+ ((ConditionalDataBindingModeViewModel) Parent).AddCondition();
+ }
+
+ public void RemoveCondition()
+ {
+ ((ConditionalDataBindingModeViewModel)Parent).RemoveCondition(DataBindingCondition);
+
+ }
+
#region IDisposable
///
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml
index 327b7ede9..b65593a90 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml
@@ -17,7 +17,7 @@
-
+
@@ -33,6 +33,7 @@
+
- Data binding result
+ Result
- Other data bindings not updating?
+ Other bindings not updating?
@@ -134,12 +135,39 @@
+
+
+
+
+
+
+
+
+
-
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs
index b25710928..2127e82c8 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs
@@ -3,6 +3,8 @@ using System.Linq;
using System.Timers;
using Artemis.Core;
using Artemis.Core.Services;
+using Artemis.Storage.Entities.Profile.DataBindings;
+using Artemis.UI.Exceptions;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline;
using Artemis.UI.Shared;
@@ -186,16 +188,15 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
return;
if (Registration.DataBinding != null && SelectedDataBindingMode == DataBindingModeType.None)
- {
RemoveDataBinding();
- CreateDataBindingModeModeViewModel();
- return;
+ else
+ {
+ if (Registration.DataBinding == null && SelectedDataBindingMode != DataBindingModeType.None)
+ EnableDataBinding();
+
+ Registration.DataBinding!.ChangeDataBindingMode(SelectedDataBindingMode);
}
- if (Registration.DataBinding == null && SelectedDataBindingMode != DataBindingModeType.None)
- EnableDataBinding();
-
- Registration.DataBinding.ChangeDataBindingMode(SelectedDataBindingMode);
CreateDataBindingModeModeViewModel();
_profileEditorService.UpdateSelectedProfileElement();
}
@@ -250,12 +251,40 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
if (Registration.DataBinding == null)
return;
+ ActiveItem = null;
Registration.LayerProperty.DisableDataBinding(Registration.DataBinding);
Update();
_profileEditorService.UpdateSelectedProfileElement();
}
+ public void CopyDataBinding()
+ {
+ if (Registration.DataBinding != null)
+ JsonClipboard.SetObject(Registration.DataBinding.Entity);
+ }
+
+ public void PasteDataBinding()
+ {
+ if (Registration.DataBinding == null)
+ Registration.LayerProperty.EnableDataBinding(Registration);
+ if (Registration.DataBinding == null)
+ throw new ArtemisUIException("Failed to create a data binding in order to paste");
+
+ DataBindingEntity dataBindingEntity = JsonClipboard.GetData();
+ if (dataBindingEntity == null)
+ return;
+
+ Registration.DataBinding.EasingTime = dataBindingEntity.EasingTime;
+ Registration.DataBinding.EasingFunction = (Easings.Functions) dataBindingEntity.EasingFunction;
+ Registration.DataBinding.ApplyDataBindingEntity(dataBindingEntity.DataBindingMode);
+ CreateDataBindingModeModeViewModel();
+ Update();
+
+
+ _profileEditorService.UpdateSelectedProfileElement();
+ }
+
private void OnFrameRendered(object sender, FrameRenderedEventArgs e)
{
UpdateTestResult();
diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeView.xaml
index f92bcd319..d33ce960e 100644
--- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeView.xaml
+++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeView.xaml
@@ -8,7 +8,7 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
-
+
@@ -32,11 +32,7 @@
Padding="6 4"
Height="22"
Command="{s:Action AddModifier}">
-
-
- ADD MODIFIER
-
-
+ ADD MODIFIER
+ IsEnabled="{Binding SelectedProfileElement, Converter={StaticResource NullToBooleanConverter}}"
+ Visibility="{Binding TimelineVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}}">
-
-
+
+ LedClicked="{s:Action OnLedClicked}"/>
diff --git a/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs
index 760f3fb96..52bae4f15 100644
--- a/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs
+++ b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs
@@ -8,6 +8,7 @@ using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Shared;
+using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using MaterialDesignThemes.Wpf;
using Ookii.Dialogs.Wpf;
@@ -197,6 +198,11 @@ namespace Artemis.UI.Screens.Settings.Device
NotifyOfPropertyChange(nameof(CanExportLayout));
}
+ public void OnLedClicked(object sender, LedClickedEventArgs e)
+ {
+ SelectedLed = e.Led;
+ }
+
#endregion
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Settings/Device/DeviceLayoutDialogView.xaml b/src/Artemis.UI/Screens/Settings/Device/DeviceLayoutDialogView.xaml
index 90f9ae552..19a8e170d 100644
--- a/src/Artemis.UI/Screens/Settings/Device/DeviceLayoutDialogView.xaml
+++ b/src/Artemis.UI/Screens/Settings/Device/DeviceLayoutDialogView.xaml
@@ -151,8 +151,8 @@
- {
- AutocompleteSource = new RegionInfoAutocompleteSource();
- SelectedRegion = AutocompleteSource.Regions.FirstOrDefault(r => r.TwoLetterISORegionName == Device.LogicalLayout ||
- r.TwoLetterISORegionName == "US" && Device.LogicalLayout == "NA");
- });
+ Task.Run(() => AutocompleteSource = new RegionInfoAutocompleteSource());
}
public ArtemisDevice Device { get; }
diff --git a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs
index c8caa051e..2f8978ef4 100644
--- a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs
+++ b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs
@@ -59,9 +59,12 @@ namespace Artemis.UI.Screens.Settings.Tabs.General
LogLevels = new BindableCollection(EnumUtilities.GetAllValuesAndDescriptions(typeof(LogEventLevel)));
ColorSchemes = new BindableCollection(EnumUtilities.GetAllValuesAndDescriptions(typeof(ApplicationColorScheme)));
- RenderScales = new List> {new("10%", 0.1)};
- for (int i = 25; i <= 100; i += 25)
- RenderScales.Add(new Tuple(i + "%", i / 100.0));
+ RenderScales = new List>
+ {
+ new("25%", 0.25),
+ new("50%", 0.5),
+ new("100%", 1),
+ };
TargetFrameRates = new List>();
for (int i = 10; i <= 30; i += 5)
diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml
index b97f80323..2060c520c 100644
--- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml
+++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml
@@ -50,13 +50,17 @@
VerticalAlignment="Center"
Margin="8"
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay, FallbackValue=Collapsed}"
- Orientation="Horizontal">
+ Orientation="Horizontal"
+ ToolTip="This feature cannot be disabled without disabling the whole plugin"
+ ToolTipService.IsEnabled="{Binding FeatureInfo.AlwaysEnabled}">
-
+
Feature enabled
diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs
index fda530a9c..a6a80b341 100644
--- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs
+++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs
@@ -16,6 +16,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
private readonly IPluginManagementService _pluginManagementService;
private bool _enabling;
private readonly IMessageService _messageService;
+ private bool _canToggleEnabled;
public PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo,
bool showShield,
@@ -50,6 +51,8 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
set => Task.Run(() => UpdateEnabled(value));
}
+ public bool CanToggleEnabled => FeatureInfo.Plugin.IsEnabled && !FeatureInfo.AlwaysEnabled;
+
public void ShowLogsFolder()
{
try
@@ -76,6 +79,9 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
_pluginManagementService.PluginFeatureEnabled += OnFeatureEnableStopped;
_pluginManagementService.PluginFeatureEnableFailed += OnFeatureEnableStopped;
+ FeatureInfo.Plugin.Enabled += PluginOnToggled;
+ FeatureInfo.Plugin.Disabled += PluginOnToggled;
+
base.OnInitialActivate();
}
@@ -85,6 +91,9 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
_pluginManagementService.PluginFeatureEnabled -= OnFeatureEnableStopped;
_pluginManagementService.PluginFeatureEnableFailed -= OnFeatureEnableStopped;
+ FeatureInfo.Plugin.Enabled -= PluginOnToggled;
+ FeatureInfo.Plugin.Disabled -= PluginOnToggled;
+
base.OnClose();
}
@@ -147,6 +156,11 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
NotifyOfPropertyChange(nameof(LoadException));
}
+ private void PluginOnToggled(object sender, EventArgs e)
+ {
+ NotifyOfPropertyChange(nameof(CanToggleEnabled));
+ }
+
#endregion
}
}
\ No newline at end of file
diff --git a/src/Artemis.sln.DotSettings b/src/Artemis.sln.DotSettings
index 3a843dade..bffc7dc38 100644
--- a/src/Artemis.sln.DotSettings
+++ b/src/Artemis.sln.DotSettings
@@ -1,6 +1,9 @@
+ Inherit
+ True
True
ERROR
+ ERROR
Built-in: Full Cleanup
True
NEVER
@@ -128,17 +131,6 @@
<Name />
</Entry.SortBy>
</Entry>
- <Entry Priority="100" DisplayName="Public Enums">
- <Entry.Match>
- <And>
- <Access Is="Public" />
- <Kind Is="Enum" />
- </And>
- </Entry.Match>
- <Entry.SortBy>
- <Name />
- </Entry.SortBy>
- </Entry>
<Entry DisplayName="Static Fields and Constants">
<Entry.Match>
<Or>
@@ -207,16 +199,41 @@
<ImplementsInterface Immediate="True" />
</Entry.SortBy>
</Entry>
+ <Entry Priority="100" DisplayName="Public Enums">
+ <Entry.Match>
+ <And>
+ <Access Is="Public" />
+ <Kind Is="Enum" />
+ </And>
+ </Entry.Match>
+ <Entry.SortBy>
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ <Region Name="Events" />
+ <Region Name="Event handlers" />
+ <Region Name="IDisposable">
+ <Region.GroupBy>
+ <ImplementsInterface />
+ </Region.GroupBy>
+ </Region>
</TypePattern>
</Patterns>
ERROR
ERROR
ERROR
+ True
+ Replace
+ True
+ True
True
+ True
False
True
+ False
False
True
+ RGB
SK
UI
True