diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index b0216b37b..9b6e44fac 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -160,15 +160,32 @@ namespace Artemis.Core.Models.Profile canvas.ClipPath(Path); // Apply transformations - var rotation = RotationProperty.CurrentValue; var anchor = AnchorPointProperty.CurrentValue; - if (rotation != 0) - canvas.RotateDegrees(rotation, anchor.X * AbsoluteRectangle.Width + LayerShape.RenderRectangle.Left, anchor.Y * AbsoluteRectangle.Height + LayerShape.RenderRectangle.Top); + var position = PositionProperty.CurrentValue; + var size = SizeProperty.CurrentValue; + var rotation = RotationProperty.CurrentValue; + // Scale the anchor and make it originate from the center of the untranslated layer shape + anchor.X = anchor.X * AbsoluteRectangle.Width + LayerShape.RenderRectangle.MidX; + anchor.Y = anchor.Y * AbsoluteRectangle.Height + LayerShape.RenderRectangle.MidY; + canvas.Translate(position.X * AbsoluteRectangle.Width, position.Y * AbsoluteRectangle.Height); + canvas.RotateDegrees(rotation, anchor.X, anchor.Y); + canvas.Scale(size.Width, size.Height, anchor.X, anchor.Y); + // Placeholder if (LayerShape?.RenderPath != null) { - canvas.DrawPath(LayerShape.RenderPath, new SKPaint {Color = new SKColor(255, 0, 0, (byte) (2.55 * OpacityProperty.CurrentValue))}); + var testColors = new List(); + for (var i = 0; i < 9; i++) + { + if (i != 8) + testColors.Add(SKColor.FromHsv(i * 32, 100, 100)); + else + 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}); } LayerBrush?.Render(canvas); @@ -300,7 +317,7 @@ namespace Artemis.Core.Models.Profile 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(PositionProperty.CurrentValue, SizeProperty.CurrentValue); + LayerShape?.CalculateRenderProperties(); OnRenderPropertiesUpdated(); } diff --git a/src/Artemis.Core/Models/Profile/LayerShapes/Ellipse.cs b/src/Artemis.Core/Models/Profile/LayerShapes/Ellipse.cs index bd182685f..2ddf66601 100644 --- a/src/Artemis.Core/Models/Profile/LayerShapes/Ellipse.cs +++ b/src/Artemis.Core/Models/Profile/LayerShapes/Ellipse.cs @@ -13,21 +13,13 @@ namespace Artemis.Core.Models.Profile.LayerShapes { } - public override void CalculateRenderProperties(SKPoint shapePosition, SKSize shapeSize) + public override void CalculateRenderProperties() { - var width = Layer.AbsoluteRectangle.Width; - var height = Layer.AbsoluteRectangle.Height; - var rect = SKRect.Create( - Layer.Rectangle.Left + shapePosition.X * width, - Layer.Rectangle.Top + shapePosition.Y * height, - shapeSize.Width * width, - shapeSize.Height * height - ); - var path = new SKPath(); - path.AddOval(rect); + RenderRectangle = GetUnscaledRectangle(); + var path = new SKPath(); + path.AddOval(RenderRectangle); RenderPath = path; - RenderRectangle = rect; } public override void ApplyToEntity() diff --git a/src/Artemis.Core/Models/Profile/LayerShapes/Fill.cs b/src/Artemis.Core/Models/Profile/LayerShapes/Fill.cs index 078137b62..f1d437ab9 100644 --- a/src/Artemis.Core/Models/Profile/LayerShapes/Fill.cs +++ b/src/Artemis.Core/Models/Profile/LayerShapes/Fill.cs @@ -13,11 +13,10 @@ namespace Artemis.Core.Models.Profile.LayerShapes { } - public override void CalculateRenderProperties(SKPoint shapePosition, SKSize shapeSize) + public override void CalculateRenderProperties() { - // TODO: Scale the path? Not sure if desirable + RenderRectangle = GetUnscaledRectangle(); RenderPath = Layer.Path; - RenderRectangle = Layer.Path.GetRect(); } public override void ApplyToEntity() diff --git a/src/Artemis.Core/Models/Profile/LayerShapes/LayerShape.cs b/src/Artemis.Core/Models/Profile/LayerShapes/LayerShape.cs index eb4231581..261a5ef4e 100644 --- a/src/Artemis.Core/Models/Profile/LayerShapes/LayerShape.cs +++ b/src/Artemis.Core/Models/Profile/LayerShapes/LayerShape.cs @@ -15,6 +15,7 @@ namespace Artemis.Core.Models.Profile.LayerShapes protected LayerShape(Layer layer, ShapeEntity shapeEntity) { Layer = layer; + ScaledRectangle = SKRect.Create(shapeEntity.X, shapeEntity.Y, shapeEntity.Width, shapeEntity.Height); } /// @@ -23,7 +24,13 @@ namespace Artemis.Core.Models.Profile.LayerShapes public Layer Layer { get; set; } /// - /// A render rectangle relative to the layer + /// A relative and scaled rectangle that defines where the shape is located in relation to the layer's size + /// Note: scaled means a range of 0.0 to 1.0. 1.0 being full width/height, 0.5 being half + /// + public SKRect ScaledRectangle { get; set; } + + /// + /// An absolute and scaled render rectangle /// public SKRect RenderRectangle { get; protected set; } @@ -32,56 +39,29 @@ namespace Artemis.Core.Models.Profile.LayerShapes /// public SKPath RenderPath { get; protected set; } - public abstract void CalculateRenderProperties(SKPoint shapePosition, SKSize shapeSize); + public abstract void CalculateRenderProperties(); public virtual void ApplyToEntity() { - Layer.LayerEntity.ShapeEntity = new ShapeEntity(); - } - - /// - /// Updates Position and Size using the provided unscaled rectangle - /// - /// An unscaled rectangle which is relative to the layer (1.0 being full width/height, 0.5 being half). - /// - /// An optional timespan to indicate where to set the properties, if null the properties' base values - /// will be used. - /// - public void SetFromUnscaledRectangle(SKRect rect, TimeSpan? time) - { - if (!Layer.Leds.Any()) + Layer.LayerEntity.ShapeEntity = new ShapeEntity { - Layer.PositionProperty.SetCurrentValue(SKPoint.Empty, time); - Layer.SizeProperty.SetCurrentValue(SKSize.Empty, time); - return; - } - - 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; - - Layer.PositionProperty.SetCurrentValue(new SKPoint((float) (100f / width * (rect.Left - x)) / 100f, (float) (100f / height * (rect.Top - y)) / 100f), time); - Layer.SizeProperty.SetCurrentValue(new SKSize((float) (100f / width * rect.Width) / 100f, (float) (100f / height * rect.Height) / 100f), time); - - CalculateRenderProperties(Layer.PositionProperty.CurrentValue, Layer.SizeProperty.CurrentValue); + X = ScaledRectangle.Left, + Y = ScaledRectangle.Top, + Width = ScaledRectangle.Width, + Height = ScaledRectangle.Height + }; } - public SKRect GetUnscaledRectangle() + protected SKRect GetUnscaledRectangle() { if (!Layer.Leds.Any()) return SKRect.Empty; - 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 SKRect.Create( - (float) (x + width * Layer.PositionProperty.CurrentValue.X), - (float) (y + height * Layer.PositionProperty.CurrentValue.Y), - (float) (width * Layer.SizeProperty.CurrentValue.Width), - (float) (height * Layer.SizeProperty.CurrentValue.Height) + 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 ); } @@ -94,31 +74,21 @@ namespace Artemis.Core.Models.Profile.LayerShapes return; } - 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; - Layer.AnchorPointProperty.SetCurrentValue(new SKPoint( - (float) (100f / width * (anchor.X - x - Layer.PositionProperty.CurrentValue.X)) / 100f, - (float) (100f / height * (anchor.Y - y - Layer.PositionProperty.CurrentValue.Y)) / 100f + 100f / Layer.AbsoluteRectangle.Width * (anchor.X - Layer.AbsoluteRectangle.Left - Layer.PositionProperty.CurrentValue.X) / 100f, + 100f / Layer.AbsoluteRectangle.Height * (anchor.Y - Layer.AbsoluteRectangle.Top - Layer.PositionProperty.CurrentValue.Y) / 100f ), time); - CalculateRenderProperties(Layer.PositionProperty.CurrentValue, Layer.SizeProperty.CurrentValue); + CalculateRenderProperties(); } public SKPoint GetUnscaledAnchor() { if (!Layer.Leds.Any()) return SKPoint.Empty; - - 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 new SKPoint( - (float) (x + width * (Layer.AnchorPointProperty.CurrentValue.X + Layer.PositionProperty.CurrentValue.X)), - (float) (y + height * (Layer.AnchorPointProperty.CurrentValue.Y + Layer.PositionProperty.CurrentValue.Y)) + Layer.AbsoluteRectangle.Left + Layer.AbsoluteRectangle.Width * (Layer.AnchorPointProperty.CurrentValue.X + Layer.PositionProperty.CurrentValue.X), + Layer.AbsoluteRectangle.Top + Layer.AbsoluteRectangle.Height * (Layer.AnchorPointProperty.CurrentValue.Y + Layer.PositionProperty.CurrentValue.Y) ); } } diff --git a/src/Artemis.Core/Models/Profile/LayerShapes/Polygon.cs b/src/Artemis.Core/Models/Profile/LayerShapes/Polygon.cs index f8d432ea8..f5248c80a 100644 --- a/src/Artemis.Core/Models/Profile/LayerShapes/Polygon.cs +++ b/src/Artemis.Core/Models/Profile/LayerShapes/Polygon.cs @@ -23,9 +23,19 @@ namespace Artemis.Core.Models.Profile.LayerShapes /// /// The points of this polygon where they need to be rendered inside the layer /// - public List RenderPoints => Points.Select(p => new SKPoint(p.X * Layer.AbsoluteRectangle.Width, p.Y * Layer.AbsoluteRectangle.Height)).ToList(); + 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(); + } + } - public override void CalculateRenderProperties(SKPoint shapePosition, SKSize shapeSize) + public override void CalculateRenderProperties() { var path = new SKPath(); path.AddPoly(RenderPoints.ToArray()); diff --git a/src/Artemis.Core/Models/Profile/LayerShapes/Rectangle.cs b/src/Artemis.Core/Models/Profile/LayerShapes/Rectangle.cs index a17fe4fbe..17c18eca4 100644 --- a/src/Artemis.Core/Models/Profile/LayerShapes/Rectangle.cs +++ b/src/Artemis.Core/Models/Profile/LayerShapes/Rectangle.cs @@ -13,21 +13,13 @@ namespace Artemis.Core.Models.Profile.LayerShapes { } - public override void CalculateRenderProperties(SKPoint shapePosition, SKSize shapeSize) + public override void CalculateRenderProperties() { - var width = Layer.AbsoluteRectangle.Width; - var height = Layer.AbsoluteRectangle.Height; - var rect = SKRect.Create( - Layer.Rectangle.Left + shapePosition.X * width, - Layer.Rectangle.Top + shapePosition.Y * height, - shapeSize.Width * width, - shapeSize.Height * height - ); - var path = new SKPath(); - path.AddRect(rect); + RenderRectangle = GetUnscaledRectangle(); + var path = new SKPath(); + path.AddRect(RenderRectangle); RenderPath = path; - RenderRectangle = rect; } public override void ApplyToEntity() diff --git a/src/Artemis.Storage/Entities/Profile/ShapeEntity.cs b/src/Artemis.Storage/Entities/Profile/ShapeEntity.cs index fc415ab80..e8d07ca68 100644 --- a/src/Artemis.Storage/Entities/Profile/ShapeEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/ShapeEntity.cs @@ -4,6 +4,10 @@ namespace Artemis.Storage.Entities.Profile { public class ShapeEntity { + public float X { get; set; } + public float Y { get; set; } + public float Width { get; set; } + public float Height { get; set; } public ShapeEntityType Type { get; set; } public List Points { get; set; } } diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index 396eb369e..481cff2a0 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -222,6 +222,7 @@ + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs index 2f7d668dd..746067ad3 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs @@ -5,9 +5,13 @@ using System.Windows.Media; using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile.LayerShapes; using Artemis.Core.Models.Surface; +using Artemis.Core.Services; using Artemis.UI.Extensions; +using Artemis.UI.Services; using Artemis.UI.Services.Interfaces; using RGB.NET.Core; +using SkiaSharp.Views.WPF; +using Stylet; using Rectangle = Artemis.Core.Models.Profile.LayerShapes.Rectangle; namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization @@ -15,10 +19,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization public class ProfileLayerViewModel : CanvasViewModel { private readonly IProfileEditorService _profileEditorService; + private readonly ILayerEditorService _layerEditorService; - public ProfileLayerViewModel(Layer layer, IProfileEditorService profileEditorService) + public ProfileLayerViewModel(Layer layer, IProfileEditorService profileEditorService, ILayerEditorService layerEditorService) { _profileEditorService = profileEditorService; + _layerEditorService = layerEditorService; Layer = layer; Update(); @@ -98,33 +104,31 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization return; } - var skRect = Layer.LayerShape.GetUnscaledRectangle(); - var rect = new Rect(skRect.Left, skRect.Top, Math.Max(0, skRect.Width), Math.Max(0, skRect.Height)); - - var shapeGeometry = Geometry.Empty; - switch (Layer.LayerShape) + Execute.PostToUIThread(() => { - case Ellipse _: - shapeGeometry = new EllipseGeometry(rect); - break; - case Fill _: - shapeGeometry = LayerGeometry; - break; - case Polygon _: - // TODO - shapeGeometry = new RectangleGeometry(rect); - break; - case Rectangle _: - shapeGeometry = new RectangleGeometry(rect); - break; - } - - // Apply transformation like done by the core during layer rendering - var anchor = Layer.LayerShape.GetUnscaledAnchor(); - shapeGeometry.Transform = new RotateTransform(Layer.RotationProperty.CurrentValue, anchor.X, anchor.Y); - - shapeGeometry.Freeze(); - ShapeGeometry = shapeGeometry; + var rect = _layerEditorService.GetShapeRenderRect(Layer.LayerShape); + var shapeGeometry = Geometry.Empty; + switch (Layer.LayerShape) + { + case Ellipse _: + shapeGeometry = new EllipseGeometry(rect); + break; + case Fill _: + shapeGeometry = LayerGeometry; + break; + case Polygon _: + // TODO + shapeGeometry = new RectangleGeometry(rect); + break; + case Rectangle _: + shapeGeometry = new RectangleGeometry(rect); + break; + } + + shapeGeometry.Transform = _layerEditorService.GetLayerTransformGroup(Layer); + shapeGeometry.Freeze(); + ShapeGeometry = shapeGeometry; + }); } private void CreateViewportRectangle() diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs index 3a13f4677..7a6e16806 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs @@ -14,6 +14,7 @@ using Artemis.UI.Events; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools; using Artemis.UI.Screens.Shared; +using Artemis.UI.Services; using Artemis.UI.Services.Interfaces; using RGB.NET.Core; using Stylet; @@ -23,6 +24,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization public class ProfileViewModel : ProfileEditorPanelViewModel, IHandle, IHandle { private readonly IProfileEditorService _profileEditorService; + private readonly ILayerEditorService _layerEditorService; private readonly IProfileLayerViewModelFactory _profileLayerViewModelFactory; private readonly ISettingsService _settingsService; private readonly ISurfaceService _surfaceService; @@ -32,12 +34,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization private TimerUpdateTrigger _updateTrigger; public ProfileViewModel(IProfileEditorService profileEditorService, + ILayerEditorService layerEditorService, ISurfaceService surfaceService, ISettingsService settingsService, IEventAggregator eventAggregator, IProfileLayerViewModelFactory profileLayerViewModelFactory) { _profileEditorService = profileEditorService; + _layerEditorService = layerEditorService; _surfaceService = surfaceService; _settingsService = settingsService; _profileLayerViewModelFactory = profileLayerViewModelFactory; @@ -246,16 +250,17 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization private void ActivateToolByIndex(int value) { + // Consider using DI if dependencies start to add up switch (value) { case 0: ActiveToolViewModel = new ViewpointMoveToolViewModel(this, _profileEditorService); break; case 1: - ActiveToolViewModel = new EditToolViewModel(this, _profileEditorService); + ActiveToolViewModel = new EditToolViewModel(this, _profileEditorService, _layerEditorService); break; case 2: - ActiveToolViewModel = new SelectionToolViewModel(this, _profileEditorService); + ActiveToolViewModel = new SelectionToolViewModel(this, _profileEditorService, _layerEditorService); break; case 3: ActiveToolViewModel = new SelectionAddToolViewModel(this, _profileEditorService); @@ -264,10 +269,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization ActiveToolViewModel = new SelectionRemoveToolViewModel(this, _profileEditorService); break; case 6: - ActiveToolViewModel = new EllipseToolViewModel(this, _profileEditorService); + ActiveToolViewModel = new EllipseToolViewModel(this, _profileEditorService, _layerEditorService); break; case 7: - ActiveToolViewModel = new RectangleToolViewModel(this, _profileEditorService); + ActiveToolViewModel = new RectangleToolViewModel(this, _profileEditorService, _layerEditorService); break; case 8: ActiveToolViewModel = new PolygonToolViewModel(this, _profileEditorService); 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 ea044d963..9efd421cd 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolView.xaml @@ -10,10 +10,10 @@ d:DesignWidth="800" d:DataContext="{d:DesignInstance {x:Type local:EditToolViewModel}}"> - + - + LayerTransformChildren = _layerEditorService.GetLayerTransformGroup(layer).Children); } } } + public void ShapeEditMouseDown(object sender, MouseButtonEventArgs e) { if (_isDragging) @@ -53,7 +60,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools if (ProfileEditorService.SelectedProfileElement is Layer layer) { var dragStartPosition = GetRelativePosition(sender, e); - var skRect = layer.LayerShape.GetUnscaledRectangle(); + var skRect = layer.LayerShape.RenderRectangle; _dragOffsetX = skRect.Left - dragStartPosition.X; _dragOffsetY = skRect.Top - dragStartPosition.Y; _dragStart = dragStartPosition; @@ -110,24 +117,25 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools return; var position = GetRelativePosition(sender, e); - var skRect = layer.LayerShape.GetUnscaledRectangle(); + var skRect = layer.LayerShape.RenderRectangle; var x = (float) (position.X + _dragOffsetX); var y = (float) (position.Y + _dragOffsetY); - if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift)) - layer.LayerShape.SetFromUnscaledRectangle(SKRect.Create(x, y, skRect.Width, skRect.Height), ProfileEditorService.CurrentTime); - else - { - if (_draggingVertically) - layer.LayerShape.SetFromUnscaledRectangle(SKRect.Create(skRect.Left, y, skRect.Width, skRect.Height), ProfileEditorService.CurrentTime); - else if (_draggingHorizontally) - layer.LayerShape.SetFromUnscaledRectangle(SKRect.Create(x, skRect.Top, skRect.Width, skRect.Height), ProfileEditorService.CurrentTime); - else - { - _draggingHorizontally = Math.Abs(position.X - _dragStart.X) > Math.Abs(position.Y - _dragStart.Y); - _draggingVertically = Math.Abs(position.X - _dragStart.X) < Math.Abs(position.Y - _dragStart.Y); - } - } + // TODO: Update the translation + // if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift)) + // layer.LayerShape.SetFromUnscaledRectangle(SKRect.Create(x, y, skRect.Width, skRect.Height), ProfileEditorService.CurrentTime); + // else + // { + // if (_draggingVertically) + // layer.LayerShape.SetFromUnscaledRectangle(SKRect.Create(skRect.Left, y, skRect.Width, skRect.Height), ProfileEditorService.CurrentTime); + // else if (_draggingHorizontally) + // layer.LayerShape.SetFromUnscaledRectangle(SKRect.Create(x, skRect.Top, skRect.Width, skRect.Height), ProfileEditorService.CurrentTime); + // else + // { + // _draggingHorizontally = Math.Abs(position.X - _dragStart.X) > Math.Abs(position.Y - _dragStart.Y); + // _draggingVertically = Math.Abs(position.X - _dragStart.X) < Math.Abs(position.Y - _dragStart.Y); + // } + // } ProfileEditorService.UpdateProfilePreview(); } @@ -142,7 +150,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools return; var position = GetRelativePosition(sender, e); - var skRect = layer.LayerShape.GetUnscaledRectangle(); + var skRect = layer.LayerShape.RenderRectangle; if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) { // Take the greatest difference @@ -164,7 +172,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools var position = GetRelativePosition(sender, e); - var skRect = layer.LayerShape.GetUnscaledRectangle(); + var skRect = layer.LayerShape.RenderRectangle; skRect.Top = (float) Math.Min(position.Y, skRect.Bottom); ApplyShapeResize(skRect); } @@ -179,7 +187,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools return; var position = GetRelativePosition(sender, e); - var skRect = layer.LayerShape.GetUnscaledRectangle(); + var skRect = layer.LayerShape.RenderRectangle; if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) { // Take the greatest difference @@ -201,7 +209,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools var position = GetRelativePosition(sender, e); - var skRect = layer.LayerShape.GetUnscaledRectangle(); + var skRect = layer.LayerShape.RenderRectangle; skRect.Right = (float) Math.Max(position.X, skRect.Left); ApplyShapeResize(skRect); } @@ -216,7 +224,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools return; var position = GetRelativePosition(sender, e); - var skRect = layer.LayerShape.GetUnscaledRectangle(); + var skRect = layer.LayerShape.RenderRectangle; if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) { // Take the greatest difference @@ -238,7 +246,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools var position = GetRelativePosition(sender, e); - var skRect = layer.LayerShape.GetUnscaledRectangle(); + var skRect = layer.LayerShape.RenderRectangle; skRect.Bottom = (float) Math.Max(position.Y, skRect.Top); ApplyShapeResize(skRect); } @@ -253,7 +261,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools return; var position = GetRelativePosition(sender, e); - var skRect = layer.LayerShape.GetUnscaledRectangle(); + var skRect = layer.LayerShape.RenderRectangle; if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) { // Take the greatest difference @@ -275,7 +283,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools var position = GetRelativePosition(sender, e); - var skRect = layer.LayerShape.GetUnscaledRectangle(); + var skRect = layer.LayerShape.RenderRectangle; skRect.Left = (float) Math.Min(position.X, skRect.Right); ApplyShapeResize(skRect); } @@ -291,15 +299,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools if (!(ProfileEditorService.SelectedProfileElement is Layer layer)) return; + // TODO: Apply the translation // Store the original position to create an offset for the anchor - var original = layer.PositionProperty.CurrentValue; - layer.LayerShape.SetFromUnscaledRectangle(newRect, ProfileEditorService.CurrentTime); - var updated = layer.PositionProperty.CurrentValue; - // Apply the offset to the anchor so it stays in at same spot - layer.AnchorPointProperty.SetCurrentValue(new SKPoint( - layer.AnchorPointProperty.CurrentValue.X + (original.X - updated.X), - layer.AnchorPointProperty.CurrentValue.Y + (original.Y - updated.Y) - ), ProfileEditorService.CurrentTime); + // var original = layer.PositionProperty.CurrentValue; + // layer.LayerShape.SetFromUnscaledRectangle(newRect, ProfileEditorService.CurrentTime); + // var updated = layer.PositionProperty.CurrentValue; + // // Apply the offset to the anchor so it stays in at same spot + // layer.AnchorPointProperty.SetCurrentValue(new SKPoint( + // layer.AnchorPointProperty.CurrentValue.X + (original.X - updated.X), + // layer.AnchorPointProperty.CurrentValue.Y + (original.Y - updated.Y) + // ), ProfileEditorService.CurrentTime); // Update the preview ProfileEditorService.UpdateProfilePreview(); diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EllipseToolViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EllipseToolViewModel.cs index b5666c6b4..93d1d703b 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EllipseToolViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EllipseToolViewModel.cs @@ -4,6 +4,7 @@ using System.Windows.Input; using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile.LayerShapes; using Artemis.UI.Properties; +using Artemis.UI.Services; using Artemis.UI.Services.Interfaces; using SkiaSharp.Views.WPF; @@ -11,10 +12,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools { public class EllipseToolViewModel : VisualizationToolViewModel { + private readonly ILayerEditorService _layerEditorService; private bool _shiftDown; - public EllipseToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService) : base(profileViewModel, profileEditorService) + public EllipseToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService, ILayerEditorService layerEditorService) + : base(profileViewModel, profileEditorService) { + _layerEditorService = layerEditorService; using (var stream = new MemoryStream(Resources.aero_crosshair)) { Cursor = new Cursor(stream); @@ -48,7 +52,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools layer.LayerShape = new Ellipse(layer); // Apply the drag rectangle - layer.LayerShape.SetFromUnscaledRectangle(DragRectangle.ToSKRect(), ProfileEditorService.CurrentTime); + _layerEditorService.SetShapeRenderRect(layer.LayerShape, DragRectangle); ProfileEditorService.UpdateSelectedProfileElement(); } } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/RectangleToolViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/RectangleToolViewModel.cs index 84682ccaf..fc5c43589 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/RectangleToolViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/RectangleToolViewModel.cs @@ -4,6 +4,7 @@ using System.Windows.Input; using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile.LayerShapes; using Artemis.UI.Properties; +using Artemis.UI.Services; using Artemis.UI.Services.Interfaces; using SkiaSharp.Views.WPF; @@ -11,10 +12,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools { public class RectangleToolViewModel : VisualizationToolViewModel { + private readonly ILayerEditorService _layerEditorService; private bool _shiftDown; - public RectangleToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService) : base(profileViewModel, profileEditorService) + public RectangleToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService, ILayerEditorService layerEditorService) + : base(profileViewModel, profileEditorService) { + _layerEditorService = layerEditorService; using (var stream = new MemoryStream(Resources.aero_crosshair)) { Cursor = new Cursor(stream); @@ -48,7 +52,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools layer.LayerShape = new Rectangle(layer); // Apply the drag rectangle - layer.LayerShape.SetFromUnscaledRectangle(DragRectangle.ToSKRect(), ProfileEditorService.CurrentTime); + _layerEditorService.SetShapeRenderRect(layer.LayerShape, DragRectangle); ProfileEditorService.UpdateSelectedProfileElement(); } } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs index fe2d1bf5a..81e2026fa 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionToolViewModel.cs @@ -6,6 +6,7 @@ using Artemis.Core.Models.Profile; using Artemis.Core.Models.Surface; using Artemis.UI.Extensions; using Artemis.UI.Properties; +using Artemis.UI.Services; using Artemis.UI.Services.Interfaces; using SkiaSharp; @@ -13,8 +14,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools { public class SelectionToolViewModel : VisualizationToolViewModel { - public SelectionToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService) : base(profileViewModel, profileEditorService) + private readonly ILayerEditorService _layerEditorService; + + public SelectionToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService, ILayerEditorService layerEditorService) + : base(profileViewModel, profileEditorService) { + _layerEditorService = layerEditorService; using (var stream = new MemoryStream(Resources.aero_crosshair)) { Cursor = new Cursor(stream); @@ -48,14 +53,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools if (ProfileEditorService.SelectedProfileElement is Layer layer) { // If the layer has a shape, save it's size - var shapeSize = SKRect.Empty; + var shapeSize = Rect.Empty; if (layer.LayerShape != null) - shapeSize = layer.LayerShape.GetUnscaledRectangle(); + shapeSize = _layerEditorService.GetShapeRenderRect(layer.LayerShape); layer.ClearLeds(); layer.AddLeds(selectedLeds); // Restore the saved size if (layer.LayerShape != null) - layer.LayerShape.SetFromUnscaledRectangle(shapeSize, ProfileEditorService.CurrentTime); + _layerEditorService.SetShapeRenderRect(layer.LayerShape, shapeSize); ProfileEditorService.UpdateSelectedProfileElement(); } diff --git a/src/Artemis.UI/Services/LayerShapeService.cs b/src/Artemis.UI/Services/LayerShapeService.cs new file mode 100644 index 000000000..af7f00d81 --- /dev/null +++ b/src/Artemis.UI/Services/LayerShapeService.cs @@ -0,0 +1,116 @@ +using System; +using System.Linq; +using System.Windows; +using System.Windows.Media; +using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Profile.LayerShapes; +using Artemis.Core.Services; +using Artemis.UI.Services.Interfaces; +using SkiaSharp; +using SkiaSharp.Views.WPF; + +namespace Artemis.UI.Services +{ + public class LayerEditorService : ILayerEditorService + { + private readonly ISettingsService _settingsService; + + public LayerEditorService(ISettingsService settingsService) + { + _settingsService = settingsService; + } + + /// + public Rect GetShapeRenderRect(LayerShape layerShape) + { + // 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) + ); + } + + public Rect GetLayerRenderRect(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.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) + ); + } + + /// + public void SetShapeRenderRect(LayerShape layerShape, Rect rect) + { + if (!layerShape.Layer.Leds.Any()) + { + layerShape.ScaledRectangle = SKRect.Empty; + return; + } + + // 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 + ); + layerShape.CalculateRenderProperties(); + } + + /// + public TransformGroup GetLayerTransformGroup(Layer layer) + { + var layerRect = GetLayerRenderRect(layer).ToSKRect(); + var shapeRect = GetShapeRenderRect(layer.LayerShape).ToSKRect(); + + // Apply transformation like done by the core during layer rendering + var anchor = layer.AnchorPointProperty.CurrentValue; + var position = layer.PositionProperty.CurrentValue; + var size = layer.SizeProperty.CurrentValue; + var rotation = layer.RotationProperty.CurrentValue; + // Scale the anchor and make it originate from the center of the untranslated layer shape + anchor.X = anchor.X * layerRect.Width + shapeRect.MidX; + anchor.Y = anchor.Y * layerRect.Height + shapeRect.MidY; + + var transformGroup = new TransformGroup(); + transformGroup.Children.Add(new ScaleTransform(size.Width, size.Height, anchor.X, anchor.Y)); + transformGroup.Children.Add(new RotateTransform(rotation, anchor.X, anchor.Y)); + transformGroup.Children.Add(new TranslateTransform(position.X * layerRect.Width, position.Y * layerRect.Height)); + + return transformGroup; + } + } + + public interface ILayerEditorService : IArtemisUIService + { + /// + /// Returns an absolute and scaled rectangle for the given shape that is corrected for the current render scale. + /// + /// + Rect GetShapeRenderRect(LayerShape layerShape); + + /// + /// Sets the render rectangle of the given shape to match the provided unscaled rectangle. The rectangle is corrected + /// for the current render scale. + /// + /// + /// + void SetShapeRenderRect(LayerShape layerShape, Rect rect); + + /// + /// Creates a WPF transform group that contains all the transformations required to render the provided layer. + /// Note: Run on UI thread. + /// + /// + /// + TransformGroup GetLayerTransformGroup(Layer layer); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Services/ProfileEditorService.cs b/src/Artemis.UI/Services/ProfileEditorService.cs index 0a795ee0e..5ef28e7af 100644 --- a/src/Artemis.UI/Services/ProfileEditorService.cs +++ b/src/Artemis.UI/Services/ProfileEditorService.cs @@ -74,7 +74,7 @@ namespace Artemis.UI.Services baseLayerProperty.KeyframeEngine?.OverrideProgress(CurrentTime); // Force layer shape to redraw - layer.LayerShape?.CalculateRenderProperties(layer.PositionProperty.CurrentValue, layer.SizeProperty.CurrentValue); + layer.LayerShape?.CalculateRenderProperties(); // Update the brush with the delta (which can now be negative ^^) layer.Update(delta.TotalSeconds); }