diff --git a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs
index eeb31bf6c..fd8485857 100644
--- a/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs
+++ b/src/Artemis.Core/Models/Profile/Conditions/DisplayConditionPredicate.cs
@@ -124,7 +124,8 @@ namespace Artemis.Core.Models.Profile.Conditions
StaticConditionLambda = null;
CompiledStaticConditionLambda = null;
- if (PredicateType == PredicateType.Dynamic)
+ // If the operator does not support a right side, create a static expression because the right side will simply be null
+ if (PredicateType == PredicateType.Dynamic && Operator.SupportsRightSide)
CreateDynamicExpression();
CreateStaticExpression();
diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs
index 315ecac2d..c95d37598 100644
--- a/src/Artemis.Core/Models/Profile/Folder.cs
+++ b/src/Artemis.Core/Models/Profile/Folder.cs
@@ -156,8 +156,10 @@ namespace Artemis.Core.Models.Profile
// Iterate the children in reverse because the first layer must be rendered last to end up on top
for (var index = Children.Count - 1; index > -1; index--)
{
+ folderCanvas.Save();
var profileElement = Children[index];
profileElement.Render(deltaTime, folderCanvas, _folderBitmap.Info);
+ folderCanvas.Restore();
}
var targetLocation = Path.Bounds.Location;
diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs
index 99265b4bc..99b21c3b3 100644
--- a/src/Artemis.Core/Models/Profile/Layer.cs
+++ b/src/Artemis.Core/Models/Profile/Layer.cs
@@ -425,12 +425,14 @@ namespace Artemis.Core.Models.Profile
OnRenderPropertiesUpdated();
}
- internal SKPoint GetLayerAnchorPosition(SKPath layerPath)
+ internal SKPoint GetLayerAnchorPosition(SKPath layerPath, bool zeroBased = false)
{
var positionProperty = Transform.Position.CurrentValue;
// Start at the center of the shape
- var position = new SKPoint(layerPath.Bounds.MidX, layerPath.Bounds.MidY);
+ var position = zeroBased
+ ? new SKPoint(layerPath.Bounds.MidX - layerPath.Bounds.Left, layerPath.Bounds.MidY - layerPath.Bounds.Top)
+ : new SKPoint(layerPath.Bounds.MidX, layerPath.Bounds.MidY);
// Apply translation
position.X += positionProperty.X * layerPath.Bounds.Width;
@@ -444,17 +446,46 @@ namespace Artemis.Core.Models.Profile
/// layer translations out
///
///
- public void ExcludePathFromTranslation(SKPath path)
+ public void IncludePathInTranslation(SKPath path, bool zeroBased)
{
var sizeProperty = Transform.Scale.CurrentValue;
var rotationProperty = Transform.Rotation.CurrentValue;
- var anchorPosition = GetLayerAnchorPosition(Path);
+ var anchorPosition = GetLayerAnchorPosition(Path, zeroBased);
var anchorProperty = Transform.AnchorPoint.CurrentValue;
// Translation originates from the unscaled center of the shape and is tied to the anchor
- var x = anchorPosition.X - Bounds.MidX - anchorProperty.X * Bounds.Width;
- var y = anchorPosition.Y - Bounds.MidY - anchorProperty.Y * Bounds.Height;
+ var x = anchorPosition.X - (zeroBased ? Bounds.MidX - Bounds.Left : Bounds.MidX) - anchorProperty.X * Bounds.Width;
+ var y = anchorPosition.Y - (zeroBased ? Bounds.MidY - Bounds.Top : Bounds.MidY) - anchorProperty.Y * Bounds.Height;
+
+ if (General.FillType == LayerFillType.Stretch)
+ {
+ path.Transform(SKMatrix.MakeTranslation(x, y));
+ path.Transform(SKMatrix.MakeScale((sizeProperty.Width / 100f), (sizeProperty.Height / 100f), anchorPosition.X, anchorPosition.Y));
+ path.Transform(SKMatrix.MakeRotationDegrees(rotationProperty, anchorPosition.X, anchorPosition.Y));
+ }
+ else
+ {
+ path.Transform(SKMatrix.MakeTranslation(x, y));
+ path.Transform(SKMatrix.MakeRotationDegrees(rotationProperty * -1, anchorPosition.X, anchorPosition.Y));
+ }
+ }
+
+ ///
+ /// Excludes the provided path from the translations applied to the layer by applying translations that cancel the
+ /// layer translations out
+ ///
+ public void ExcludePathFromTranslation(SKPath path, bool zeroBased)
+ {
+ var sizeProperty = Transform.Scale.CurrentValue;
+ var rotationProperty = Transform.Rotation.CurrentValue;
+
+ var anchorPosition = GetLayerAnchorPosition(Path, zeroBased);
+ var anchorProperty = Transform.AnchorPoint.CurrentValue;
+
+ // Translation originates from the unscaled center of the shape and is tied to the anchor
+ var x = anchorPosition.X - (zeroBased ? Bounds.MidX - Bounds.Left : Bounds.MidX) - anchorProperty.X * Bounds.Width;
+ var y = anchorPosition.Y - (zeroBased ? Bounds.MidY - Bounds.Top : Bounds.MidY) - anchorProperty.Y * Bounds.Height;
var reversedXScale = 1f / (sizeProperty.Width / 100f);
var reversedYScale = 1f / (sizeProperty.Height / 100f);
@@ -472,6 +503,40 @@ namespace Artemis.Core.Models.Profile
}
}
+ ///
+ /// Excludes the provided canvas from the translations applied to the layer by applying translations that cancel the
+ /// layer translations out
+ ///
+ /// The number of transformations applied
+ public int ExcludeCanvasFromTranslation(SKCanvas canvas, bool zeroBased)
+ {
+ var sizeProperty = Transform.Scale.CurrentValue;
+ var rotationProperty = Transform.Rotation.CurrentValue;
+
+ var anchorPosition = GetLayerAnchorPosition(Path, zeroBased);
+ var anchorProperty = Transform.AnchorPoint.CurrentValue;
+
+ // Translation originates from the unscaled center of the shape and is tied to the anchor
+ var x = anchorPosition.X - (zeroBased ? Bounds.MidX - Bounds.Left : Bounds.MidX) - anchorProperty.X * Bounds.Width;
+ var y = anchorPosition.Y - (zeroBased ? Bounds.MidY - Bounds.Top : Bounds.MidY) - anchorProperty.Y * Bounds.Height;
+
+ var reversedXScale = 1f / (sizeProperty.Width / 100f);
+ var reversedYScale = 1f / (sizeProperty.Height / 100f);
+
+ if (General.FillType == LayerFillType.Stretch)
+ {
+ canvas.Translate(x * -1, y * -1);
+ canvas.Scale(reversedXScale, reversedYScale, anchorPosition.X, anchorPosition.Y);
+ canvas.RotateDegrees(rotationProperty * -1, anchorPosition.X, anchorPosition.Y);
+
+ return 3;
+ }
+
+ canvas.RotateDegrees(rotationProperty * -1, anchorPosition.X, anchorPosition.Y);
+ canvas.Translate(x * -1, y * -1);
+ return 2;
+ }
+
#endregion
#region LED management
diff --git a/src/Artemis.Core/Plugins/Abstract/DeviceProvider.cs b/src/Artemis.Core/Plugins/Abstract/DeviceProvider.cs
index 7a6c5d58b..d9bde1593 100644
--- a/src/Artemis.Core/Plugins/Abstract/DeviceProvider.cs
+++ b/src/Artemis.Core/Plugins/Abstract/DeviceProvider.cs
@@ -30,7 +30,7 @@ namespace Artemis.Core.Plugins.Abstract
protected void ResolveAbsolutePath(Type type, object sender, ResolvePathEventArgs e)
{
- if (sender.GetType().IsGenericType(type))
+ if (sender.GetType() == type || sender.GetType().IsGenericType(type))
{
// Start from the plugin directory
if (e.RelativePart != null && e.FileName != null)
diff --git a/src/Artemis.Core/Plugins/LayerBrush/Abstract/PerLedLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrush/Abstract/PerLedLayerBrush.cs
new file mode 100644
index 000000000..65c4614f4
--- /dev/null
+++ b/src/Artemis.Core/Plugins/LayerBrush/Abstract/PerLedLayerBrush.cs
@@ -0,0 +1,74 @@
+using Artemis.Core.Models.Profile;
+using Artemis.Core.Models.Surface;
+using Artemis.Core.Services.Interfaces;
+using SkiaSharp;
+
+namespace Artemis.Core.Plugins.LayerBrush.Abstract
+{
+ public abstract class PerLedLayerBrush : PropertiesLayerBrush where T : LayerPropertyGroup
+ {
+ protected PerLedLayerBrush()
+ {
+ BrushType = LayerBrushType.Regular;
+ }
+
+
+ ///
+ /// The main method of rendering for this type of brush. Called once per frame for each LED in the layer
+ ///
+ /// Note: Due to transformations, the render point may not match the position of the LED, always use the render
+ /// point to determine where the color will go.
+ ///
+ ///
+ /// The LED that will receive the color
+ /// The point at which the color is located
+ /// The color the LED will receive
+ public abstract SKColor GetColor(ArtemisLed led, SKPoint renderPoint);
+
+ internal override void InternalRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint)
+ {
+ // This lil' snippet renders per LED, it's neater but doesn't support translations
+ Layer.ExcludeCanvasFromTranslation(canvas, true);
+
+ var shapePath = new SKPath(Layer.LayerShape.Path);
+ Layer.IncludePathInTranslation(shapePath, true);
+ canvas.ClipPath(shapePath);
+
+ using var pointsPath = new SKPath();
+ using var ledPaint = new SKPaint();
+ foreach (var artemisLed in Layer.Leds)
+ {
+ pointsPath.AddPoly(new[]
+ {
+ new SKPoint(0, 0),
+ new SKPoint(artemisLed.AbsoluteRenderRectangle.Left - Layer.Bounds.Left, artemisLed.AbsoluteRenderRectangle.Top - Layer.Bounds.Top)
+ });
+ }
+
+ Layer.ExcludePathFromTranslation(pointsPath, true);
+ var points = pointsPath.Points;
+ for (var index = 0; index < Layer.Leds.Count; index++)
+ {
+ var artemisLed = Layer.Leds[index];
+ var renderPoint = points[index * 2 + 1];
+
+ // Let the brush determine the color
+ ledPaint.Color = GetColor(artemisLed, renderPoint);
+
+ var ledRectangle = SKRect.Create(
+ artemisLed.AbsoluteRenderRectangle.Left - Layer.Bounds.Left,
+ artemisLed.AbsoluteRenderRectangle.Top - Layer.Bounds.Top,
+ artemisLed.AbsoluteRenderRectangle.Width,
+ artemisLed.AbsoluteRenderRectangle.Height
+ );
+
+ canvas.DrawRect(ledRectangle, ledPaint);
+ }
+ }
+
+ internal override void Initialize(IRenderElementService renderElementService)
+ {
+ InitializeProperties(renderElementService);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs
index 7fd7f4956..a61dd5561 100644
--- a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs
+++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs
@@ -65,13 +65,28 @@ namespace Artemis.UI.Shared.Controls
_timer.Stop();
}
+ public static Size ResizeKeepAspect(Size src, double maxWidth, double maxHeight)
+ {
+ double scale;
+ if (maxWidth == double.PositiveInfinity && maxHeight != double.PositiveInfinity)
+ scale = maxHeight / src.Height;
+ else if (maxWidth != double.PositiveInfinity && maxHeight == double.PositiveInfinity)
+ scale = maxWidth / src.Width;
+ else if (maxWidth == double.PositiveInfinity && maxHeight == double.PositiveInfinity)
+ return src;
+
+ scale = Math.Min(maxWidth / src.Width, maxHeight / src.Height);
+
+ return new Size(src.Width * scale, src.Height * scale);
+ }
+
protected override void OnRender(DrawingContext drawingContext)
{
if (Device == null)
return;
// Determine the scale required to fit the desired size of the control
- var measureSize = MeasureOverride(Size.Empty);
+ var measureSize = MeasureDevice();
var scale = Math.Min(DesiredSize.Width / measureSize.Width, DesiredSize.Height / measureSize.Height);
var scaledRect = new Rect(0, 0, measureSize.Width * scale, measureSize.Height * scale);
@@ -103,16 +118,24 @@ namespace Artemis.UI.Shared.Controls
drawingContext.DrawDrawing(_backingStore);
}
+ private Size MeasureDevice()
+ {
+ if (Device == null)
+ return Size.Empty;
+
+ var rotationRect = new Rect(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height);
+ rotationRect.Transform(new RotateTransform(Device.Rotation).Value);
+
+ return rotationRect.Size;
+ }
+
protected override Size MeasureOverride(Size availableSize)
{
if (Device == null)
return Size.Empty;
-
- var rotationRect = new Rect(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height);
- rotationRect.Transform(new RotateTransform(Device.Rotation).Value);
- // TODO: If availableSize exceeds what we need, scale up
- return rotationRect.Size;
+ var deviceSize = MeasureDevice();
+ return ResizeKeepAspect(deviceSize, availableSize.Width, availableSize.Height);
}
private void OnUnloaded(object sender, RoutedEventArgs e)
diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs
index ca637bef0..231d81237 100644
--- a/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs
+++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs
@@ -69,7 +69,7 @@ namespace Artemis.UI.Shared.Controls
// Create transparent pixels covering the entire LedRect so the image size matched the LedRect size
drawingContext.DrawRectangle(new SolidColorBrush(Colors.Transparent), null, LedRect);
// Translate to the top-left of the LedRect
- drawingContext.PushTransform(new TranslateTransform(LedRect.X, LedRect.Y));
+ drawingContext.PushTransform(new TranslateTransform(LedRect.X , LedRect.Y));
// Render the LED geometry
drawingContext.DrawGeometry(fillBrush, new Pen(penBrush, 1) {LineJoin = PenLineJoin.Round}, DisplayGeometry.GetOutlinedPathGeometry());
// Restore the drawing context
@@ -88,7 +88,7 @@ namespace Artemis.UI.Shared.Controls
if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad)
CreateCustomGeometry(2.0);
else
- CreateCustomGeometry(0);
+ CreateCustomGeometry(1.0);
break;
case Shape.Rectangle:
if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad)
diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs
index 4c4d05455..f9d651199 100644
--- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs
+++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading.Tasks;
using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.Core.Plugins.Abstract;
@@ -125,7 +126,7 @@ namespace Artemis.UI.Shared.Services
{
if (SelectedProfile == null)
return;
-
+
// Stick to the main segment for any element that is not currently selected
foreach (var folder in SelectedProfile.GetAllFolders())
folder.OverrideProgress(CurrentTime, folder != SelectedProfileElement);
diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs
index e9770db83..01f869ce1 100644
--- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs
+++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs
@@ -65,7 +65,7 @@ namespace Artemis.UI.Ninject.Factories
public interface IProfileLayerVmFactory : IVmFactory
{
- ProfileLayerViewModel Create(Layer layer);
+ ProfileLayerViewModel Create(Layer layer, ProfileViewModel profileViewModel);
}
public interface IVisualizationToolVmFactory : IVmFactory
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml
index c83604a27..96e3c2a4c 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/DisplayConditions/DisplayConditionPredicateView.xaml
@@ -104,43 +104,45 @@
-
-
-
-
+ Visibility="{Binding SelectedOperator.SupportsRightSide, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
+
+
+
+
+