diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index be34834c3..8654becaa 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; +using System.Net.Sockets; using Artemis.Core.Exceptions; using Artemis.Core.Extensions; using Artemis.Core.Models.Profile.LayerProperties; @@ -20,6 +21,8 @@ namespace Artemis.Core.Models.Profile private readonly Dictionary _properties; private LayerShape _layerShape; private List _leds; + private SKPath _path; + private SKRect _bounds; public Layer(Profile profile, ProfileElement parent, string name) { @@ -81,22 +84,25 @@ namespace Artemis.Core.Models.Profile public ReadOnlyCollection Leds => _leds.AsReadOnly(); /// - /// An absolute rectangle to the surface that contains all the LEDs in this layer. - /// For rendering, use the RenderRectangle on . + /// A path containing all the LEDs this layer is applied to, any rendering outside the layer Path is clipped. + /// For rendering, use the Path on . /// - public SKRect AbsoluteRectangle { get; private set; } + public SKPath Path + { + get => _path; + private set + { + _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; + } + } /// - /// A zero-based rectangle that contains all the LEDs in this layer. - /// For rendering, use the RenderRectangle on . + /// The bounds of this layer /// - public SKRect Rectangle { get; private set; } - - /// - /// A path containing all the LEDs this layer is applied to. - /// For rendering, use the RenderPath on . - /// - public SKPath Path { get; private set; } + public SKRect Bounds => _bounds; /// /// Defines the shape that is rendered by the . @@ -153,7 +159,7 @@ namespace Artemis.Core.Models.Profile { property.KeyframeEngine?.Update(deltaTime); // This is a placeholder method of repeating the animation until repeat modes are implemented - if (property.KeyframeEngine != null && property.IsUsingKeyframes && property.KeyframeEngine.NextKeyframe == null) + if (property.KeyframeEngine != null && property.IsUsingKeyframes && property.KeyframeEngine.NextKeyframe == null) property.KeyframeEngine.OverrideProgress(TimeSpan.Zero); } @@ -173,19 +179,18 @@ namespace Artemis.Core.Models.Profile var size = SizeProperty.CurrentValue; var rotation = RotationProperty.CurrentValue; - var anchor = GetLayerAnchor(true); - var relativeAnchor = GetLayerAnchor(false); + var anchor = GetLayerAnchor(); // Translation originates from the unscaled center of the shape and is tied to the anchor - var x = position.X * AbsoluteRectangle.Width - LayerShape.RenderRectangle.Width / 2 - relativeAnchor.X; - var y = position.Y * AbsoluteRectangle.Height - LayerShape.RenderRectangle.Height / 2 - relativeAnchor.Y; - - canvas.RotateDegrees(rotation, anchor.X, anchor.Y); - canvas.Scale(size.Width, size.Height, anchor.X, anchor.Y); - canvas.Translate(x, y); + var x = position.X * Bounds.Width - LayerShape.Bounds.Width / 2 - anchor.X; + var y = position.Y * Bounds.Height - LayerShape.Bounds.Height / 2 - anchor.Y; + canvas.Translate(Bounds.Left + x, Bounds.Top + y); + canvas.Scale(size.Width, size.Height, anchor.X, anchor.Y); + canvas.RotateDegrees(rotation, anchor.X, anchor.Y); + // Placeholder - if (LayerShape?.RenderPath != null) + if (LayerShape?.Path != null) { var testColors = new List(); for (var i = 0; i < 9; i++) @@ -196,31 +201,23 @@ namespace Artemis.Core.Models.Profile testColors.Add(SKColor.FromHsv(0, 100, 100)); } - var shader = SKShader.CreateSweepGradient(new SKPoint(LayerShape.RenderRectangle.MidX, LayerShape.RenderRectangle.MidY), testColors.ToArray()); - canvas.DrawPath(LayerShape.RenderPath, new SKPaint {Shader = shader, Color = new SKColor(0, 0, 0, (byte) (OpacityProperty.CurrentValue * 2.55f))}); + var shader = SKShader.CreateSweepGradient(new SKPoint(LayerShape.Bounds.MidX, LayerShape.Bounds.MidY), testColors.ToArray()); + canvas.DrawPath(LayerShape.Path, new SKPaint {Shader = shader, Color = new SKColor(0, 0, 0, (byte) (OpacityProperty.CurrentValue * 2.55f))}); } LayerBrush?.Render(canvas); canvas.Restore(); } - private SKPoint GetLayerAnchor(bool absolute) + private SKPoint GetLayerAnchor() { if (LayerShape == null) return SKPoint.Empty; - if (!absolute) - { - var anchor = AnchorPointProperty.CurrentValue; - anchor.X = anchor.X * AbsoluteRectangle.Width; - anchor.Y = anchor.Y * AbsoluteRectangle.Height; - return new SKPoint(anchor.X, anchor.Y); - } - - var position = PositionProperty.CurrentValue; - position.X = position.X * AbsoluteRectangle.Width; - position.Y = position.Y * AbsoluteRectangle.Height; - return new SKPoint(position.X + LayerShape.RenderRectangle.Left, position.Y + LayerShape.RenderRectangle.Top); + var anchor = AnchorPointProperty.CurrentValue; + anchor.X = anchor.X * Bounds.Width; + anchor.Y = anchor.Y * Bounds.Height; + return new SKPoint(anchor.X, anchor.Y); } internal override void ApplyToEntity() @@ -325,31 +322,22 @@ namespace Artemis.Core.Models.Profile { if (!Leds.Any()) { - AbsoluteRectangle = SKRect.Empty; - Rectangle = SKRect.Empty; Path = new SKPath(); + + LayerShape?.CalculateRenderProperties(); OnRenderPropertiesUpdated(); return; } - // Determine to top-left and bottom-right - var minX = Leds.Min(l => l.AbsoluteRenderRectangle.Left); - var minY = Leds.Min(l => l.AbsoluteRenderRectangle.Top); - var maxX = Leds.Max(l => l.AbsoluteRenderRectangle.Right); - var maxY = Leds.Max(l => l.AbsoluteRenderRectangle.Bottom); - - AbsoluteRectangle = SKRect.Create(minX, minY, maxX - minX, maxY - minY); - Rectangle = SKRect.Create(0, 0, maxX - minX, maxY - minY); - var path = new SKPath {FillType = SKPathFillType.Winding}; foreach (var artemisLed in Leds) path.AddRect(artemisLed.AbsoluteRenderRectangle); Path = path; + // This is called here so that the shape's render properties are up to date when other code // responds to OnRenderPropertiesUpdated LayerShape?.CalculateRenderProperties(); - OnRenderPropertiesUpdated(); } diff --git a/src/Artemis.Core/Models/Profile/LayerShapes/Ellipse.cs b/src/Artemis.Core/Models/Profile/LayerShapes/Ellipse.cs index e5fb33544..a046fa45c 100644 --- a/src/Artemis.Core/Models/Profile/LayerShapes/Ellipse.cs +++ b/src/Artemis.Core/Models/Profile/LayerShapes/Ellipse.cs @@ -15,12 +15,9 @@ namespace Artemis.Core.Models.Profile.LayerShapes public override void CalculateRenderProperties() { - var unscaled = GetUnscaledRectangle(); - RenderRectangle = SKRect.Create(0,0 , unscaled.Width, unscaled.Height); - var path = new SKPath(); - path.AddOval(RenderRectangle); - RenderPath = path; + path.AddOval(GetUnscaledRectangle()); + Path = path; } internal override void ApplyToEntity() diff --git a/src/Artemis.Core/Models/Profile/LayerShapes/Fill.cs b/src/Artemis.Core/Models/Profile/LayerShapes/Fill.cs index 765a0ea3c..e194c32eb 100644 --- a/src/Artemis.Core/Models/Profile/LayerShapes/Fill.cs +++ b/src/Artemis.Core/Models/Profile/LayerShapes/Fill.cs @@ -15,12 +15,8 @@ namespace Artemis.Core.Models.Profile.LayerShapes public override void CalculateRenderProperties() { - RenderRectangle = GetUnscaledRectangle(); - // Shape originates from the center so compensate the path for that - var renderPath = new SKPath(Layer.Path); - renderPath.Transform(SKMatrix.MakeTranslation(RenderRectangle.Left - Layer.Path.Bounds.Left, RenderRectangle.Top - Layer.Path.Bounds.Top)); - RenderPath = renderPath; + Path = new SKPath(Layer.Path); } internal override void ApplyToEntity() diff --git a/src/Artemis.Core/Models/Profile/LayerShapes/LayerShape.cs b/src/Artemis.Core/Models/Profile/LayerShapes/LayerShape.cs index 6dda4b14b..538a1b726 100644 --- a/src/Artemis.Core/Models/Profile/LayerShapes/LayerShape.cs +++ b/src/Artemis.Core/Models/Profile/LayerShapes/LayerShape.cs @@ -1,12 +1,13 @@ using System.Linq; using Artemis.Storage.Entities.Profile; -using RGB.NET.Core; using SkiaSharp; namespace Artemis.Core.Models.Profile.LayerShapes { public abstract class LayerShape { + private SKPath _path; + protected LayerShape(Layer layer) { Layer = layer; @@ -30,19 +31,27 @@ namespace Artemis.Core.Models.Profile.LayerShapes public SKRect ScaledRectangle { get; set; } /// - /// An absolute and scaled render rectangle + /// A path outlining the shape /// - public SKRect RenderRectangle { get; protected set; } + public SKPath Path + { + get => _path; + protected set + { + _path = value; + Bounds = value?.Bounds ?? SKRect.Empty; + } + } /// - /// A path relative to the layer + /// The bounds of this shape /// - public SKPath RenderPath { get; protected set; } + public SKRect Bounds { get; private set; } public abstract void CalculateRenderProperties(); /// - /// Updates Position and Size using the provided unscaled rectangle + /// Updates Position and Size using the provided unscaled rectangle /// /// An unscaled rectangle where 1px = 1mm public void SetFromUnscaledRectangle(SKRect rect) @@ -54,10 +63,10 @@ namespace Artemis.Core.Models.Profile.LayerShapes } ScaledRectangle = SKRect.Create( - 100f / Layer.AbsoluteRectangle.Width * (rect.Left - Layer.AbsoluteRectangle.Left) / 100f, - 100f / Layer.AbsoluteRectangle.Height * (rect.Top - Layer.AbsoluteRectangle.Top) / 100f, - 100f / Layer.AbsoluteRectangle.Width * rect.Width / 100f, - 100f / Layer.AbsoluteRectangle.Height * rect.Height / 100f + 100f / Layer.Bounds.Width * rect.Left / 100f, + 100f / Layer.Bounds.Height * rect.Top / 100f, + 100f / Layer.Bounds.Width * rect.Width / 100f, + 100f / Layer.Bounds.Height * rect.Height / 100f ); } @@ -67,10 +76,10 @@ namespace Artemis.Core.Models.Profile.LayerShapes return SKRect.Empty; return SKRect.Create( - Layer.AbsoluteRectangle.Left + Layer.AbsoluteRectangle.Width * ScaledRectangle.Left, - Layer.AbsoluteRectangle.Top + Layer.AbsoluteRectangle.Height * ScaledRectangle.Top, - Layer.AbsoluteRectangle.Width * ScaledRectangle.Width, - Layer.AbsoluteRectangle.Height * ScaledRectangle.Height + Layer.Bounds.Width * ScaledRectangle.Left, + Layer.Bounds.Height * ScaledRectangle.Top, + Layer.Bounds.Width * ScaledRectangle.Width, + Layer.Bounds.Height * ScaledRectangle.Height ); } diff --git a/src/Artemis.Core/Models/Profile/LayerShapes/Polygon.cs b/src/Artemis.Core/Models/Profile/LayerShapes/Polygon.cs index 6a527f5e6..e593b20be 100644 --- a/src/Artemis.Core/Models/Profile/LayerShapes/Polygon.cs +++ b/src/Artemis.Core/Models/Profile/LayerShapes/Polygon.cs @@ -25,23 +25,14 @@ namespace Artemis.Core.Models.Profile.LayerShapes /// public List RenderPoints { - get - { - var x = Layer.Leds.Min(l => l.RgbLed.AbsoluteLedRectangle.Location.X); - var y = Layer.Leds.Min(l => l.RgbLed.AbsoluteLedRectangle.Location.Y); - var width = Layer.Leds.Max(l => l.RgbLed.AbsoluteLedRectangle.Location.X + l.RgbLed.AbsoluteLedRectangle.Size.Width) - x; - var height = Layer.Leds.Max(l => l.RgbLed.AbsoluteLedRectangle.Location.Y + l.RgbLed.AbsoluteLedRectangle.Size.Height) - y; - return Points.Select(p => new SKPoint((float) (p.X * width), (float) (p.Y * height))).ToList(); - } + get { return Points.Select(p => new SKPoint(p.X * Layer.Bounds.Width, p.Y * Layer.Bounds.Height)).ToList(); } } public override void CalculateRenderProperties() { var path = new SKPath(); path.AddPoly(RenderPoints.ToArray()); - - RenderPath = path; - RenderRectangle = path.GetRect(); + Path = path; } internal override void ApplyToEntity() diff --git a/src/Artemis.Core/Models/Profile/LayerShapes/Rectangle.cs b/src/Artemis.Core/Models/Profile/LayerShapes/Rectangle.cs index aab4432ca..bc568a7fb 100644 --- a/src/Artemis.Core/Models/Profile/LayerShapes/Rectangle.cs +++ b/src/Artemis.Core/Models/Profile/LayerShapes/Rectangle.cs @@ -15,11 +15,9 @@ namespace Artemis.Core.Models.Profile.LayerShapes public override void CalculateRenderProperties() { - RenderRectangle = GetUnscaledRectangle(); - var path = new SKPath(); - path.AddRect(RenderRectangle); - RenderPath = path; + path.AddRect(GetUnscaledRectangle()); + Path = path; } internal override void ApplyToEntity() diff --git a/src/Artemis.Plugins.Devices.Wooting/x64/wooting-rgb-sdk64.dll b/src/Artemis.Plugins.Devices.Wooting/x64/wooting-rgb-sdk64.dll index 4e2894dc9..0307dfb15 100644 Binary files a/src/Artemis.Plugins.Devices.Wooting/x64/wooting-rgb-sdk64.dll and b/src/Artemis.Plugins.Devices.Wooting/x64/wooting-rgb-sdk64.dll differ diff --git a/src/Artemis.Plugins.Devices.Wooting/x86/wooting-rgb-sdk.dll b/src/Artemis.Plugins.Devices.Wooting/x86/wooting-rgb-sdk.dll index cc3b64898..da8b0bc14 100644 Binary files a/src/Artemis.Plugins.Devices.Wooting/x86/wooting-rgb-sdk.dll and b/src/Artemis.Plugins.Devices.Wooting/x86/wooting-rgb-sdk.dll differ diff --git a/src/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs b/src/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs index d0d6f7d0a..c1ac4e068 100644 --- a/src/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs +++ b/src/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs @@ -35,7 +35,7 @@ namespace Artemis.Plugins.LayerBrushes.Color private void CreateShader() { - var center = new SKPoint(Layer.AbsoluteRectangle.MidX, Layer.AbsoluteRectangle.MidY); + var center = new SKPoint(Layer.LayerShape.Bounds.MidX, Layer.LayerShape.Bounds.MidY); SKShader shader; switch (Settings.GradientType) { @@ -43,10 +43,10 @@ namespace Artemis.Plugins.LayerBrushes.Color shader = SKShader.CreateColor(_testColors.First()); break; case GradientType.LinearGradient: - shader = SKShader.CreateLinearGradient(new SKPoint(0, 0), new SKPoint(Layer.AbsoluteRectangle.Width, 0), _testColors.ToArray(), SKShaderTileMode.Repeat); + shader = SKShader.CreateLinearGradient(new SKPoint(0, 0), new SKPoint(Layer.LayerShape.Bounds.Width, 0), _testColors.ToArray(), SKShaderTileMode.Repeat); break; case GradientType.RadialGradient: - shader = SKShader.CreateRadialGradient(center, Math.Min(Layer.AbsoluteRectangle.Width, Layer.AbsoluteRectangle.Height), _testColors.ToArray(), SKShaderTileMode.Repeat); + shader = SKShader.CreateRadialGradient(center, Math.Min(Layer.LayerShape.Bounds.Width, Layer.LayerShape.Bounds.Height), _testColors.ToArray(), SKShaderTileMode.Repeat); break; case GradientType.SweepGradient: shader = SKShader.CreateSweepGradient(center, _testColors.ToArray(), null, SKShaderTileMode.Clamp, 0, 360); @@ -70,7 +70,7 @@ namespace Artemis.Plugins.LayerBrushes.Color public override void Render(SKCanvas canvas) { - canvas.DrawPath(Layer.LayerShape.RenderPath, _paint); + canvas.DrawPath(Layer.LayerShape.Path, _paint); } } } \ No newline at end of file diff --git a/src/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs b/src/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs index 173c46bbe..5c78af444 100644 --- a/src/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs +++ b/src/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs @@ -43,8 +43,8 @@ namespace Artemis.Plugins.LayerBrushes.Noise public override void Render(SKCanvas canvas) { // Scale down the render path to avoid computing a value for every pixel - var width = (int) (Math.Max(Layer.AbsoluteRectangle.Width, Layer.AbsoluteRectangle.Height) / Scale); - var height = (int) (Math.Max(Layer.AbsoluteRectangle.Width, Layer.AbsoluteRectangle.Height) / Scale); + var width = (int) (Math.Max(Layer.Bounds.Width, Layer.Bounds.Height) / Scale); + var height = (int) (Math.Max(Layer.Bounds.Width, Layer.Bounds.Height) / Scale); var opacity = (float) Math.Round(Settings.Color.Alpha / 255.0, 2, MidpointRounding.AwayFromZero); using (var bitmap = new SKBitmap(new SKImageInfo(width, height))) { @@ -77,7 +77,7 @@ namespace Artemis.Plugins.LayerBrushes.Noise using (var sh = SKShader.CreateBitmap(bitmap, SKShaderTileMode.Mirror, SKShaderTileMode.Mirror, SKMatrix.MakeScale(Scale, Scale))) using (var paint = new SKPaint {Shader = sh, BlendMode = Settings.BlendMode}) { - canvas.DrawPath(Layer.LayerShape.RenderPath, paint); + canvas.DrawPath(Layer.LayerShape.Path, paint); } } } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/PropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/PropertyInputViewModel.cs index 6c882f827..4396fdca3 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/PropertyInputViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/PropertyInputViewModel.cs @@ -44,7 +44,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P { LayerPropertyViewModel.LayerProperty.SetCurrentValue(value, ProfileEditorService.CurrentTime); // Force the keyframe engine to update, the edited keyframe might affect the current keyframe progress - LayerPropertyViewModel.LayerProperty.KeyframeEngine.Update(0); + LayerPropertyViewModel.LayerProperty.KeyframeEngine?.Update(0); ProfileEditorService.UpdateSelectedProfileElement(); } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerView.xaml index 406461f47..804079a70 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerView.xaml @@ -32,32 +32,35 @@ - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs index ae374de77..5c3a6465c 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs @@ -37,6 +37,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization public Layer Layer { get; } + public Rect LayerBounds { get; set; } public Geometry LayerGeometry { get; set; } public Geometry OpacityGeometry { get; set; } public Geometry ShapeGeometry { get; set; } @@ -55,6 +56,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization { if (!Layer.Leds.Any()) { + LayerBounds = Rect.Empty; LayerGeometry = Geometry.Empty; OpacityGeometry = Geometry.Empty; ViewportRectangle = Rect.Empty; @@ -91,6 +93,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization var layerGeometry = group.GetOutlinedPathGeometry(); var opacityGeometry = Geometry.Combine(Geometry.Empty, layerGeometry, GeometryCombineMode.Exclude, new TranslateTransform()); + LayerBounds = _layerEditorService.GetLayerBounds(Layer); LayerGeometry = layerGeometry; OpacityGeometry = opacityGeometry; } @@ -105,27 +108,27 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization Execute.PostToUIThread(() => { - var rect = _layerEditorService.GetShapeUntransformedRect(Layer.LayerShape); + var bounds = _layerEditorService.GetLayerShapeBounds(Layer.LayerShape); var shapeGeometry = Geometry.Empty; switch (Layer.LayerShape) { case Ellipse _: - shapeGeometry = new EllipseGeometry(rect); + shapeGeometry = new EllipseGeometry(bounds); break; case Fill _: // Shape originates from the center so compensate the geometry for that, create a copy shapeGeometry = LayerGeometry.Clone(); // Add a transformation - shapeGeometry.Transform = new TranslateTransform(rect.Left - shapeGeometry.Bounds.Left, rect.Top - shapeGeometry.Bounds.Top); + shapeGeometry.Transform = new TranslateTransform(bounds.Left - shapeGeometry.Bounds.Left, bounds.Top - shapeGeometry.Bounds.Top); // Apply the transformation so that it won't be overridden shapeGeometry = shapeGeometry.GetOutlinedPathGeometry(); break; case Polygon _: // TODO - shapeGeometry = new RectangleGeometry(rect); + shapeGeometry = new RectangleGeometry(bounds); break; case Rectangle _: - shapeGeometry = new RectangleGeometry(rect); + shapeGeometry = new RectangleGeometry(bounds); break; } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolView.xaml index f1b5563d0..ece81e433 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolView.xaml @@ -11,7 +11,9 @@ d:DesignWidth="800" d:DataContext="{d:DesignInstance {x:Type local:EditToolViewModel}}"> - Update(); } + public Rect LayerBounds { get; set; } public SKPath ShapePath { get; set; } public SKPoint ShapeAnchor { get; set; } public RectangleGeometry ShapeGeometry { get; set; } @@ -42,11 +43,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools if (!(ProfileEditorService.SelectedProfileElement is Layer layer)) return; + LayerBounds = _layerEditorService.GetLayerBounds(layer); ShapePath = _layerEditorService.GetLayerPath(layer, true, true, true); - ShapeAnchor = _layerEditorService.GetLayerAnchor(layer, true); + ShapeAnchor = _layerEditorService.GetLayerAnchor(layer).ToSKPoint(); Execute.PostToUIThread(() => { - var shapeGeometry = new RectangleGeometry(_layerEditorService.GetShapeUntransformedRect(layer.LayerShape)) + var shapeGeometry = new RectangleGeometry(_layerEditorService.GetLayerShapeBounds(layer.LayerShape)) { Transform = _layerEditorService.GetLayerTransformGroup(layer) }; @@ -57,7 +59,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools // Store the last top-left for easy later on _topLeft = _layerEditorService.GetLayerPath(layer, true, true, true).Points[0]; } - + #region Rotation private bool _rotating; @@ -67,7 +69,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools { _rotating = true; if (ProfileEditorService.SelectedProfileElement is Layer layer) - _previousDragAngle = CalculateAngle(_layerEditorService.GetLayerAnchor(layer, true), GetRelativePosition(sender, e.MouseEventArgs).ToSKPoint()); + _previousDragAngle = CalculateAngle(_layerEditorService.GetLayerAnchor(layer), GetRelativePosition(sender, e.MouseEventArgs)); else _previousDragAngle = 0; } @@ -84,7 +86,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools return; var previousDragAngle = _previousDragAngle; - var newRotation = CalculateAngle(_layerEditorService.GetLayerAnchor(layer, true), GetRelativePosition(sender, e.MouseEventArgs).ToSKPoint()); + var newRotation = CalculateAngle(_layerEditorService.GetLayerAnchor(layer), GetRelativePosition(sender, e.MouseEventArgs)); _previousDragAngle = newRotation; // Allow the user to rotate the shape in increments of 5 @@ -278,7 +280,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools // Measure from the top-left of the shape (without rotation) _dragOffset = topLeft + (dragStartPosition - topLeft); // Get the absolute layer anchor and make it relative to the unrotated shape - _dragStartAnchor = _layerEditorService.GetLayerAnchor(layer, true) - topLeft; + _dragStartAnchor = _layerEditorService.GetLayerAnchor(layer).ToSKPoint() - topLeft; // Ensure the anchor starts in the center of the shape it is now relative to _dragStartAnchor.X -= path.Bounds.Width / 2f; _dragStartAnchor.Y -= path.Bounds.Height / 2f; @@ -390,7 +392,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools return mouseEventArgs.GetPosition((IInputElement) parent); } - private float CalculateAngle(SKPoint start, SKPoint arrival) + private float CalculateAngle(Point start, Point arrival) { var radian = (float) Math.Atan2(start.Y - arrival.Y, start.X - arrival.X); var angle = radian * (180f / (float) Math.PI); diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/FillToolViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/FillToolViewModel.cs index b4cf601d3..f62a98286 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/FillToolViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/FillToolViewModel.cs @@ -42,7 +42,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools layer.LayerShape = new Fill(layer); // Apply the full layer rectangle - _layerEditorService.SetShapeBaseFromRectangle(layer.LayerShape, _layerEditorService.GetLayerRect(layer)); + _layerEditorService.SetShapeBaseFromRectangle(layer.LayerShape, _layerEditorService.GetLayerBounds(layer)); ProfileEditorService.UpdateSelectedProfileElement(); } } diff --git a/src/Artemis.UI/Services/LayerShapeService.cs b/src/Artemis.UI/Services/LayerShapeService.cs index d5d34f36c..6f4ce0420 100644 --- a/src/Artemis.UI/Services/LayerShapeService.cs +++ b/src/Artemis.UI/Services/LayerShapeService.cs @@ -21,121 +21,56 @@ namespace Artemis.UI.Services } /// - public Rect GetLayerRenderRect(Layer layer) + public Rect GetLayerBounds(Layer layer) { // Adjust the render rectangle for the difference in render scale var renderScale = _settingsService.GetSetting("Core.RenderScale", 1.0).Value; return new Rect( - layer.Rectangle.Left / renderScale * 1, - layer.Rectangle.Top / renderScale * 1, - Math.Max(0, layer.Rectangle.Width / renderScale * 1), - Math.Max(0, layer.Rectangle.Height / renderScale * 1) + layer.Bounds.Left / renderScale * 1, + layer.Bounds.Top / renderScale * 1, + Math.Max(0, layer.Bounds.Width / renderScale * 1), + Math.Max(0, layer.Bounds.Height / renderScale * 1) ); } - public Rect GetLayerRect(Layer layer) + /// + public Rect GetLayerShapeBounds(LayerShape layerShape) { // Adjust the render rectangle for the difference in render scale var renderScale = _settingsService.GetSetting("Core.RenderScale", 1.0).Value; return new Rect( - layer.AbsoluteRectangle.Left / renderScale * 1, - layer.AbsoluteRectangle.Top / renderScale * 1, - Math.Max(0, layer.AbsoluteRectangle.Width / renderScale * 1), - Math.Max(0, layer.AbsoluteRectangle.Height / renderScale * 1) + layerShape.Bounds.Left / renderScale * 1, + layerShape.Bounds.Top / renderScale * 1, + Math.Max(0, layerShape.Bounds.Width / renderScale * 1), + Math.Max(0, layerShape.Bounds.Height / renderScale * 1) ); } /// - public SKPath GetLayerPath(Layer layer, bool includeTranslation, bool includeScale, bool includeRotation) + public Point GetLayerAnchor(Layer layer) { - var layerRect = GetLayerRenderRect(layer).ToSKRect(); - var shapeRect = GetShapeUntransformedRect(layer.LayerShape).ToSKRect(); - - // Apply transformation like done by the core during layer rendering - var anchor = GetLayerAnchor(layer, true); - var relativeAnchor = GetLayerAnchor(layer, false); - - // Translation originates from the unscaled center of the shape and is tied to the anchor - var x = layer.PositionProperty.CurrentValue.X * layerRect.Width - shapeRect.Width / 2 - relativeAnchor.X; - var y = layer.PositionProperty.CurrentValue.Y * layerRect.Height - shapeRect.Height / 2 - relativeAnchor.Y; - - var path = new SKPath(); - path.AddRect(shapeRect); - if (includeTranslation) - path.Transform(SKMatrix.MakeTranslation(x, y)); - if (includeScale) - path.Transform(SKMatrix.MakeScale(layer.SizeProperty.CurrentValue.Width, layer.SizeProperty.CurrentValue.Height, anchor.X, anchor.Y)); - if (includeRotation) - path.Transform(SKMatrix.MakeRotationDegrees(layer.RotationProperty.CurrentValue, anchor.X, anchor.Y)); - - return path; - } - - public void ReverseLayerPath(Layer layer, SKPath path) - { - var layerRect = GetLayerRenderRect(layer).ToSKRect(); - var shapeRect = GetShapeUntransformedRect(layer.LayerShape).ToSKRect(); - - // Apply transformation like done by the core during layer rendering - var anchor = GetLayerAnchor(layer, true); - var relativeAnchor = GetLayerAnchor(layer, false); - - // Translation originates from the unscaled center of the shape and is tied to the anchor - var x = layer.PositionProperty.CurrentValue.X * layerRect.Width - shapeRect.Width / 2 - relativeAnchor.X; - var y = layer.PositionProperty.CurrentValue.Y * layerRect.Height - shapeRect.Height / 2 - relativeAnchor.Y; - - SKMatrix.MakeScale(layer.SizeProperty.CurrentValue.Width, layer.SizeProperty.CurrentValue.Height, anchor.X, anchor.Y).TryInvert(out var scale); - - path.Transform(SKMatrix.MakeRotationDegrees(layer.RotationProperty.CurrentValue * -1, anchor.X, anchor.Y)); - path.Transform(scale); - path.Transform(SKMatrix.MakeTranslation(x * -1, y * -1)); - } - - /// - public SKPoint GetLayerAnchor(Layer layer, bool absolute) - { - var layerRect = GetLayerRect(layer); - if (absolute) - { - var position = layer.PositionProperty.CurrentValue; - position.X = (float) (position.X * layerRect.Width); - position.Y = (float) (position.Y * layerRect.Height); - var shapeRect = GetShapeUntransformedRect(layer.LayerShape); - return new SKPoint((float) (position.X + shapeRect.Left), (float) (position.Y + shapeRect.Top)); - } + var layerBounds = GetLayerBounds(layer); + // TODO figure out what else is needed, position should matter here var anchor = layer.AnchorPointProperty.CurrentValue; - anchor.X = (float) (anchor.X * layerRect.Width); - anchor.Y = (float) (anchor.Y * layerRect.Height); - return new SKPoint(anchor.X, anchor.Y); - } + anchor.X = (float) (anchor.X * layerBounds.Width); + anchor.Y = (float) (anchor.Y * layerBounds.Height); - public void SetLayerAnchor(Layer layer, SKPoint point, bool absolute, TimeSpan? time) - { - var layerRect = GetLayerRect(layer); - if (absolute) - { - var shapeRect = GetShapeUntransformedRect(layer.LayerShape); - var position = new SKPoint((float) ((point.X - shapeRect.Left) / layerRect.Width), (float) ((point.Y - shapeRect.Top) / layerRect.Height)); - layer.PositionProperty.SetCurrentValue(position, time); - } - - var anchor = new SKPoint((float) (point.X / layerRect.Width), (float) (point.Y / layerRect.Height)); - layer.AnchorPointProperty.SetCurrentValue(anchor, time); + return new Point(anchor.X * layerBounds.Width, anchor.Y * layerBounds.Height); } /// public TransformGroup GetLayerTransformGroup(Layer layer) { - var layerRect = GetLayerRenderRect(layer).ToSKRect(); - var shapeRect = GetShapeUntransformedRect(layer.LayerShape).ToSKRect(); + var layerBounds = GetLayerBounds(layer).ToSKRect(); + var shapeBounds = GetLayerShapeBounds(layer.LayerShape).ToSKRect(); // Apply transformation like done by the core during layer rendering - var anchor = GetLayerAnchor(layer, true); + var anchor = GetLayerAnchor(layer); // Translation originates from the unscaled center of the shape and is tied to the anchor - var x = layer.PositionProperty.CurrentValue.X * layerRect.Width - shapeRect.Width / 2 - GetLayerAnchor(layer, false).X; - var y = layer.PositionProperty.CurrentValue.Y * layerRect.Height - shapeRect.Height / 2 - GetLayerAnchor(layer, false).Y; + var x = layer.PositionProperty.CurrentValue.X * layerBounds.Width - shapeBounds.Width / 2 - anchor.X; + var y = layer.PositionProperty.CurrentValue.Y * layerBounds.Height - shapeBounds.Height / 2 - anchor.Y; var transformGroup = new TransformGroup(); transformGroup.Children.Add(new TranslateTransform(x, y)); @@ -146,27 +81,30 @@ namespace Artemis.UI.Services } /// - public Rect GetShapeUntransformedRect(LayerShape layerShape) + public SKPath GetLayerPath(Layer layer, bool includeTranslation, bool includeScale, bool includeRotation) { - if (layerShape == null) - return Rect.Empty; + var layerBounds = GetLayerBounds(layer).ToSKRect(); + var shapeBounds = GetLayerShapeBounds(layer.LayerShape).ToSKRect(); - // Adjust the render rectangle for the difference in render scale - var renderScale = _settingsService.GetSetting("Core.RenderScale", 1.0).Value; - return new Rect( - layerShape.RenderRectangle.Left / renderScale * 1, - layerShape.RenderRectangle.Top / renderScale * 1, - Math.Max(0, layerShape.RenderRectangle.Width / renderScale * 1), - Math.Max(0, layerShape.RenderRectangle.Height / renderScale * 1) - ); + // Apply transformation like done by the core during layer rendering + var anchor = GetLayerAnchor(layer).ToSKPoint(); + + // Translation originates from the unscaled center of the shape and is tied to the anchor + var x = layer.PositionProperty.CurrentValue.X * layerBounds.Width - shapeBounds.Width / 2 - anchor.X; + var y = layer.PositionProperty.CurrentValue.Y * layerBounds.Height - shapeBounds.Height / 2 - anchor.Y; + + var path = new SKPath(); + path.AddRect(shapeBounds); + if (includeTranslation) + path.Transform(SKMatrix.MakeTranslation(x, y)); + if (includeScale) + path.Transform(SKMatrix.MakeScale(layer.SizeProperty.CurrentValue.Width, layer.SizeProperty.CurrentValue.Height, anchor.X, anchor.Y)); + if (includeRotation) + path.Transform(SKMatrix.MakeRotationDegrees(layer.RotationProperty.CurrentValue, anchor.X, anchor.Y)); + + return path; } - /// - public Rect GetShapeTransformedRect(LayerShape layerShape) - { - var path = GetLayerPath(layerShape.Layer, true, true, false); - return path.Bounds.ToRect(); - } /// public void SetShapeBaseFromRectangle(LayerShape layerShape, Rect rect) @@ -177,16 +115,16 @@ namespace Artemis.UI.Services return; } - var layerRect = GetLayerRenderRect(layerShape.Layer).ToSKRect(); + var layerBounds = GetLayerBounds(layerShape.Layer).ToSKRect(); // Compensate for the current value of the position transformation rect.X += rect.Width / 2; - rect.X -= layerRect.Width * layerShape.Layer.PositionProperty.CurrentValue.X; - rect.X += layerRect.Width * layerShape.Layer.AnchorPointProperty.CurrentValue.X * layerShape.Layer.SizeProperty.CurrentValue.Width; + rect.X -= layerBounds.Width * layerShape.Layer.PositionProperty.CurrentValue.X; + rect.X += layerBounds.Width * layerShape.Layer.AnchorPointProperty.CurrentValue.X * layerShape.Layer.SizeProperty.CurrentValue.Width; rect.Y += rect.Height / 2; - rect.Y -= layerRect.Height * layerShape.Layer.PositionProperty.CurrentValue.Y; - rect.Y += layerRect.Height * layerShape.Layer.AnchorPointProperty.CurrentValue.Y * layerShape.Layer.SizeProperty.CurrentValue.Height; + rect.Y -= layerBounds.Height * layerShape.Layer.PositionProperty.CurrentValue.Y; + rect.Y += layerBounds.Height * layerShape.Layer.AnchorPointProperty.CurrentValue.Y * layerShape.Layer.SizeProperty.CurrentValue.Height; // Compensate for the current value of the size transformation rect.Height /= layerShape.Layer.SizeProperty.CurrentValue.Height; @@ -195,10 +133,10 @@ namespace Artemis.UI.Services // Adjust the provided rect for the difference in render scale var renderScale = _settingsService.GetSetting("Core.RenderScale", 1.0).Value; layerShape.ScaledRectangle = SKRect.Create( - 100f / layerShape.Layer.AbsoluteRectangle.Width * ((float) (rect.Left * renderScale) - layerShape.Layer.AbsoluteRectangle.Left) / 100f, - 100f / layerShape.Layer.AbsoluteRectangle.Height * ((float) (rect.Top * renderScale) - layerShape.Layer.AbsoluteRectangle.Top) / 100f, - 100f / layerShape.Layer.AbsoluteRectangle.Width * (float) (rect.Width * renderScale) / 100f, - 100f / layerShape.Layer.AbsoluteRectangle.Height * (float) (rect.Height * renderScale) / 100f + 100f / layerShape.Layer.Bounds.Width * ((float) (rect.Left * renderScale) - layerShape.Layer.Bounds.Left) / 100f, + 100f / layerShape.Layer.Bounds.Height * ((float) (rect.Top * renderScale) - layerShape.Layer.Bounds.Top) / 100f, + 100f / layerShape.Layer.Bounds.Width * (float) (rect.Width * renderScale) / 100f, + 100f / layerShape.Layer.Bounds.Height * (float) (rect.Height * renderScale) / 100f ); layerShape.CalculateRenderProperties(); } @@ -210,14 +148,14 @@ namespace Artemis.UI.Services if (absolute) { return new SKPoint( - 100f / layer.AbsoluteRectangle.Width * ((float) (point.X * renderScale) - layer.AbsoluteRectangle.Left) / 100f, - 100f / layer.AbsoluteRectangle.Height * ((float) (point.Y * renderScale) - layer.AbsoluteRectangle.Top) / 100f + 100f / layer.Bounds.Width * ((float) (point.X * renderScale) - layer.Bounds.Left) / 100f, + 100f / layer.Bounds.Height * ((float) (point.Y * renderScale) - layer.Bounds.Top) / 100f ); } return new SKPoint( - 100f / layer.AbsoluteRectangle.Width * (float) (point.X * renderScale) / 100f, - 100f / layer.AbsoluteRectangle.Height * (float) (point.Y * renderScale) / 100f + 100f / layer.Bounds.Width * (float) (point.X * renderScale) / 100f, + 100f / layer.Bounds.Height * (float) (point.Y * renderScale) / 100f ); } } @@ -225,40 +163,25 @@ namespace Artemis.UI.Services public interface ILayerEditorService : IArtemisUIService { /// - /// Returns an relative and scaled rectangle for the given layer that is corrected for the current render scale. + /// Returns the layer's bounds, corrected for the current render scale. /// /// /// - Rect GetLayerRenderRect(Layer layer); + Rect GetLayerBounds(Layer layer); /// - /// Returns an absolute and scaled rectangle for the given layer that is corrected for the current render scale. + /// Returns the layer's anchor, corrected for the current render scale. /// /// /// - Rect GetLayerRect(Layer layer); + Point GetLayerAnchor(Layer layer); /// - /// Returns an absolute and scaled rectangular path for the given layer that is corrected for the current render scale. + /// Returns the layer shape's bounds, corrected for the current render scale. /// - /// - /// - /// - /// + /// /// - SKPath GetLayerPath(Layer layer, bool includeTranslation, bool includeScale, bool includeRotation); - - void ReverseLayerPath(Layer layer, SKPath path); - - /// - /// Returns an absolute and scaled anchor for the given layer, optionally with the translation applied. - /// - /// - /// - /// - SKPoint GetLayerAnchor(Layer layer, bool absolute); - - void SetLayerAnchor(Layer layer, SKPoint point, bool absolute, TimeSpan? time); + Rect GetLayerShapeBounds(LayerShape layerShape); /// /// Creates a WPF transform group that contains all the transformations required to render the provided layer. @@ -269,18 +192,14 @@ namespace Artemis.UI.Services TransformGroup GetLayerTransformGroup(Layer layer); /// - /// Returns an absolute and scaled rectangle for the given shape that is corrected for the current render scale without - /// any transformations applied. + /// Returns an absolute and scaled rectangular path for the given layer that is corrected for the current render scale. /// + /// + /// + /// + /// /// - Rect GetShapeUntransformedRect(LayerShape layerShape); - - /// - /// Returns an absolute and scaled rectangle for the given shape that is corrected for the current render scale with - /// translation and scale transformations applied. - /// - /// - Rect GetShapeTransformedRect(LayerShape layerShape); + SKPath GetLayerPath(Layer layer, bool includeTranslation, bool includeScale, bool includeRotation); /// /// Sets the base properties of the given shape to match the provided unscaled rectangle. The rectangle is corrected