From f2df51d40c97605df51fa911ac4b2c4a134338a7 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Fri, 31 Jan 2020 00:02:01 +0100 Subject: [PATCH] Simplified the different properties used for layer/shape rendering Cleaned up UI layer shape service --- src/Artemis.Core/Models/Profile/Layer.cs | 86 +++---- .../Models/Profile/LayerShapes/Ellipse.cs | 7 +- .../Models/Profile/LayerShapes/Fill.cs | 6 +- .../Models/Profile/LayerShapes/LayerShape.cs | 37 +-- .../Models/Profile/LayerShapes/Polygon.cs | 13 +- .../Models/Profile/LayerShapes/Rectangle.cs | 6 +- .../x64/wooting-rgb-sdk64.dll | Bin 24064 -> 25088 bytes .../x86/wooting-rgb-sdk.dll | Bin 19456 -> 20480 bytes .../ColorBrush.cs | 8 +- .../NoiseBrush.cs | 6 +- .../PropertyInput/PropertyInputViewModel.cs | 2 +- .../Visualization/ProfileLayerView.xaml | 53 +++-- .../Visualization/ProfileLayerViewModel.cs | 13 +- .../Visualization/Tools/EditToolView.xaml | 4 +- .../Visualization/Tools/EditToolViewModel.cs | 16 +- .../Visualization/Tools/FillToolViewModel.cs | 2 +- src/Artemis.UI/Services/LayerShapeService.cs | 217 ++++++------------ 17 files changed, 192 insertions(+), 284 deletions(-) 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 4e2894dc9d9e6325e6387fbfb285646e7b20df5c..0307dfb1542d542578ddd8d695226bf5d473f36f 100644 GIT binary patch delta 11218 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 JfZ$8Re*-3Q#QgvO 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 cc3b64898d0c3272f49077c84e5fca76e02d8796..da8b0bc14363bf9f3facd3ff952e5207bd51af98 100644 GIT binary patch delta 8143 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 - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + 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