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}}"> - - +