mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-01-02 10:43:31 +00:00
Transform tool - Mostly implemented resizing
This commit is contained in:
parent
12e91b8c81
commit
1716eba8ec
@ -45,30 +45,14 @@ public static class LayerExtensions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns an absolute and scaled rectangular path for the given layer in real coordinates.
|
/// Returns an absolute and scaled rectangular path for the given layer in real coordinates.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static SKPath GetLayerPath(this Layer layer, bool includeTranslation, bool includeScale, bool includeRotation, SKPoint? anchorOverride = null)
|
public static SKPath GetLayerPath(this Layer layer, bool includeTranslation, bool includeScale, bool includeRotation)
|
||||||
{
|
{
|
||||||
SKRect layerBounds = GetLayerBounds(layer);
|
SKRect layerBounds = GetLayerBounds(layer);
|
||||||
|
|
||||||
// Apply transformation like done by the core during layer rendering (same differences apply as in GetLayerTransformGroup)
|
SKMatrix transform = layer.GetTransformMatrix(false, includeTranslation, includeScale, includeRotation, layerBounds);
|
||||||
SKPoint anchorPosition = GetLayerAnchorPosition(layer);
|
|
||||||
if (anchorOverride != null)
|
|
||||||
anchorPosition = anchorOverride.Value;
|
|
||||||
|
|
||||||
SKPoint anchorProperty = layer.Transform.AnchorPoint.CurrentValue;
|
|
||||||
|
|
||||||
// Translation originates from the unscaled center of the shape and is tied to the anchor
|
|
||||||
float x = anchorPosition.X - layerBounds.MidX - anchorProperty.X * layerBounds.Width;
|
|
||||||
float y = anchorPosition.Y - layerBounds.MidY - anchorProperty.Y * layerBounds.Height;
|
|
||||||
|
|
||||||
SKPath path = new();
|
SKPath path = new();
|
||||||
path.AddRect(layerBounds);
|
path.AddRect(layerBounds);
|
||||||
if (includeTranslation)
|
path.Transform(transform);
|
||||||
path.Transform(SKMatrix.CreateTranslation(x, y));
|
|
||||||
if (includeScale)
|
|
||||||
path.Transform(SKMatrix.CreateScale(layer.Transform.Scale.CurrentValue.Width / 100f, layer.Transform.Scale.CurrentValue.Height / 100f, anchorPosition.X, anchorPosition.Y));
|
|
||||||
if (includeRotation)
|
|
||||||
path.Transform(SKMatrix.CreateRotationDegrees(layer.Transform.Rotation.CurrentValue, anchorPosition.X, anchorPosition.Y));
|
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,19 +75,27 @@ public static class LayerExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the offset from the given point to the top-left of the layer
|
/// Returns the offset from the given point to the closest sides of the layer's shape bounds
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static SKPoint GetDragOffset(this Layer layer, SKPoint dragStart)
|
public static SKPoint GetDragOffset(this Layer layer, SKPoint dragStart)
|
||||||
{
|
{
|
||||||
// Figure out what the top left will be if the shape moves to the current cursor position
|
SKRect bounds = layer.GetLayerPath(true, true, false).Bounds;
|
||||||
SKPoint scaledDragStart = GetScaledPoint(layer, dragStart, true);
|
SKPoint anchor = layer.GetLayerAnchorPosition();
|
||||||
SKPoint tempAnchor = GetLayerAnchorPosition(layer, scaledDragStart);
|
|
||||||
SKPoint tempTopLeft = GetLayerPath(layer, true, true, true, tempAnchor)[0];
|
|
||||||
|
|
||||||
// Get the shape's position
|
float xOffset = 0f, yOffset = 0f;
|
||||||
SKPoint topLeft = GetLayerPath(layer, true, true, true)[0];
|
|
||||||
|
|
||||||
// The difference between the two is the offset
|
// X offset
|
||||||
return topLeft - tempTopLeft;
|
if (dragStart.X < anchor.X)
|
||||||
|
xOffset = bounds.Left - dragStart.X;
|
||||||
|
else if (dragStart.X > anchor.X)
|
||||||
|
xOffset = bounds.Right - dragStart.X;
|
||||||
|
|
||||||
|
// Y offset
|
||||||
|
if (dragStart.Y < anchor.Y)
|
||||||
|
yOffset = bounds.Top - dragStart.Y;
|
||||||
|
else if (dragStart.Y > anchor.Y)
|
||||||
|
yOffset = bounds.Bottom - dragStart.Y;
|
||||||
|
|
||||||
|
return new SKPoint(xOffset, yOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,23 +1,63 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Shared.Extensions;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.PanAndZoom;
|
using Avalonia.Controls.PanAndZoom;
|
||||||
|
using Avalonia.Controls.Shapes;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.LogicalTree;
|
using Avalonia.LogicalTree;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
|
using Avalonia.Skia;
|
||||||
|
using Avalonia.Styling;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools;
|
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools;
|
||||||
|
|
||||||
public class TransformToolView : ReactiveUserControl<TransformToolViewModel>
|
public class TransformToolView : ReactiveUserControl<TransformToolViewModel>
|
||||||
{
|
{
|
||||||
private ZoomBorder? _zoomBorder;
|
private ZoomBorder? _zoomBorder;
|
||||||
private PointerPoint _dragOffset;
|
private SKPoint _dragStart;
|
||||||
|
private SKPoint _dragOffset;
|
||||||
|
|
||||||
|
private readonly Ellipse _rotateTopLeft;
|
||||||
|
private readonly Ellipse _rotateTopRight;
|
||||||
|
private readonly Ellipse _rotateBottomRight;
|
||||||
|
private readonly Ellipse _rotateBottomLeft;
|
||||||
|
|
||||||
|
private readonly Rectangle _resizeTopCenter;
|
||||||
|
private readonly Rectangle _resizeRightCenter;
|
||||||
|
private readonly Rectangle _resizeBottomCenter;
|
||||||
|
private readonly Rectangle _resizeLeftCenter;
|
||||||
|
private readonly Rectangle _resizeTopLeft;
|
||||||
|
private readonly Rectangle _resizeTopRight;
|
||||||
|
private readonly Rectangle _resizeBottomRight;
|
||||||
|
private readonly Rectangle _resizeBottomLeft;
|
||||||
|
|
||||||
|
private readonly Ellipse _anchorPoint;
|
||||||
|
|
||||||
public TransformToolView()
|
public TransformToolView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
_rotateTopLeft = this.Get<Ellipse>("RotateTopLeft");
|
||||||
|
_rotateTopRight = this.Get<Ellipse>("RotateTopRight");
|
||||||
|
_rotateBottomRight = this.Get<Ellipse>("RotateBottomRight");
|
||||||
|
_rotateBottomLeft = this.Get<Ellipse>("RotateBottomLeft");
|
||||||
|
|
||||||
|
_resizeTopCenter = this.Get<Rectangle>("ResizeTopCenter");
|
||||||
|
_resizeRightCenter = this.Get<Rectangle>("ResizeRightCenter");
|
||||||
|
_resizeBottomCenter = this.Get<Rectangle>("ResizeBottomCenter");
|
||||||
|
_resizeLeftCenter = this.Get<Rectangle>("ResizeLeftCenter");
|
||||||
|
_resizeTopLeft = this.Get<Rectangle>("ResizeTopLeft");
|
||||||
|
_resizeTopRight = this.Get<Rectangle>("ResizeTopRight");
|
||||||
|
_resizeBottomRight = this.Get<Rectangle>("ResizeBottomRight");
|
||||||
|
_resizeBottomLeft = this.Get<Rectangle>("ResizeBottomLeft");
|
||||||
|
|
||||||
|
_anchorPoint = this.Get<Ellipse>("AnchorPoint");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
@ -30,7 +70,7 @@ public class TransformToolView : ReactiveUserControl<TransformToolViewModel>
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
|
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
|
||||||
{
|
{
|
||||||
_zoomBorder = (ZoomBorder?)this.GetLogicalAncestors().FirstOrDefault(l => l is ZoomBorder);
|
_zoomBorder = (ZoomBorder?) this.GetLogicalAncestors().FirstOrDefault(l => l is ZoomBorder);
|
||||||
if (_zoomBorder != null)
|
if (_zoomBorder != null)
|
||||||
_zoomBorder.PropertyChanged += ZoomBorderOnPropertyChanged;
|
_zoomBorder.PropertyChanged += ZoomBorderOnPropertyChanged;
|
||||||
base.OnAttachedToLogicalTree(e);
|
base.OnAttachedToLogicalTree(e);
|
||||||
@ -58,11 +98,12 @@ public class TransformToolView : ReactiveUserControl<TransformToolViewModel>
|
|||||||
|
|
||||||
private void RotationOnPointerPressed(object? sender, PointerPressedEventArgs e)
|
private void RotationOnPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
{
|
{
|
||||||
IInputElement? element = (IInputElement?)sender;
|
Shape? element = (Shape?) sender;
|
||||||
if (element == null || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
if (element == null || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed || ViewModel?.Layer == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_dragOffset = e.GetCurrentPoint(_zoomBorder);
|
_dragStart = e.GetCurrentPoint(_zoomBorder).Position.ToSKPoint();
|
||||||
|
_dragOffset = ViewModel.Layer.GetDragOffset(_dragStart);
|
||||||
|
|
||||||
e.Pointer.Capture(element);
|
e.Pointer.Capture(element);
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
@ -70,8 +111,8 @@ public class TransformToolView : ReactiveUserControl<TransformToolViewModel>
|
|||||||
|
|
||||||
private void RotationOnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
private void RotationOnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
{
|
{
|
||||||
IInputElement? element = (IInputElement?)sender;
|
Shape? element = (Shape?) sender;
|
||||||
if (element == null || e.Pointer.Captured != element || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
if (element == null || !ReferenceEquals(e.Pointer.Captured, element) || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
e.Pointer.Capture(null);
|
e.Pointer.Capture(null);
|
||||||
@ -80,8 +121,8 @@ public class TransformToolView : ReactiveUserControl<TransformToolViewModel>
|
|||||||
|
|
||||||
private void RotationOnPointerMoved(object? sender, PointerEventArgs e)
|
private void RotationOnPointerMoved(object? sender, PointerEventArgs e)
|
||||||
{
|
{
|
||||||
IInputElement? element = (IInputElement?) sender;
|
Shape? element = (Shape?) sender;
|
||||||
if (element == null || e.Pointer.Captured != element || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
if (element == null || !ReferenceEquals(e.Pointer.Captured, element) || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
@ -93,11 +134,12 @@ public class TransformToolView : ReactiveUserControl<TransformToolViewModel>
|
|||||||
|
|
||||||
private void MoveOnPointerPressed(object? sender, PointerPressedEventArgs e)
|
private void MoveOnPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
{
|
{
|
||||||
IInputElement? element = (IInputElement?)sender;
|
Shape? element = (Shape?) sender;
|
||||||
if (element == null || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
if (element == null || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed || ViewModel?.Layer == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_dragOffset = e.GetCurrentPoint(_zoomBorder);
|
_dragStart = e.GetCurrentPoint(_zoomBorder).Position.ToSKPoint();
|
||||||
|
_dragOffset = ViewModel.Layer.GetDragOffset(_dragStart);
|
||||||
|
|
||||||
e.Pointer.Capture(element);
|
e.Pointer.Capture(element);
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
@ -105,8 +147,8 @@ public class TransformToolView : ReactiveUserControl<TransformToolViewModel>
|
|||||||
|
|
||||||
private void MoveOnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
private void MoveOnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
{
|
{
|
||||||
IInputElement? element = (IInputElement?)sender;
|
Shape? element = (Shape?) sender;
|
||||||
if (element == null || e.Pointer.Captured != element || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
if (element == null || !ReferenceEquals(e.Pointer.Captured, element) || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
e.Pointer.Capture(null);
|
e.Pointer.Capture(null);
|
||||||
@ -115,8 +157,8 @@ public class TransformToolView : ReactiveUserControl<TransformToolViewModel>
|
|||||||
|
|
||||||
private void MoveOnPointerMoved(object? sender, PointerEventArgs e)
|
private void MoveOnPointerMoved(object? sender, PointerEventArgs e)
|
||||||
{
|
{
|
||||||
IInputElement? element = (IInputElement?)sender;
|
Shape? element = (Shape?) sender;
|
||||||
if (element == null || e.Pointer.Captured != element || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
if (element == null || !ReferenceEquals(e.Pointer.Captured, element) || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
@ -128,11 +170,15 @@ public class TransformToolView : ReactiveUserControl<TransformToolViewModel>
|
|||||||
|
|
||||||
private void ResizeOnPointerPressed(object? sender, PointerPressedEventArgs e)
|
private void ResizeOnPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
{
|
{
|
||||||
IInputElement? element = (IInputElement?)sender;
|
Shape? element = (Shape?) sender;
|
||||||
if (element == null || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
if (element == null || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed || ViewModel?.Layer == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_dragOffset = e.GetCurrentPoint(_zoomBorder);
|
_dragStart = CounteractLayerRotation(e.GetCurrentPoint(this).Position.ToSKPoint(), ViewModel.Layer);
|
||||||
|
_dragOffset = ViewModel.Layer.GetDragOffset(_dragStart);
|
||||||
|
|
||||||
|
SKPoint position = GetPositionForViewModel(e);
|
||||||
|
ViewModel.StartResize(position);
|
||||||
|
|
||||||
e.Pointer.Capture(element);
|
e.Pointer.Capture(element);
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
@ -140,22 +186,70 @@ public class TransformToolView : ReactiveUserControl<TransformToolViewModel>
|
|||||||
|
|
||||||
private void ResizeOnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
private void ResizeOnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
{
|
{
|
||||||
IInputElement? element = (IInputElement?)sender;
|
Shape? element = (Shape?) sender;
|
||||||
if (element == null || e.Pointer.Captured != element || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
if (element == null || !ReferenceEquals(e.Pointer.Captured, element) || e.InitialPressMouseButton != MouseButton.Left || ViewModel?.Layer == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
SKPoint position = GetPositionForViewModel(e);
|
||||||
|
ViewModel.FinishResize(position, GetResizeDirection(element), e.KeyModifiers.HasFlag(KeyModifiers.Shift));
|
||||||
|
|
||||||
e.Pointer.Capture(null);
|
e.Pointer.Capture(null);
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResizeOnPointerMoved(object? sender, PointerEventArgs e)
|
private void ResizeOnPointerMoved(object? sender, PointerEventArgs e)
|
||||||
{
|
{
|
||||||
IInputElement? element = (IInputElement?)sender;
|
Shape? element = (Shape?) sender;
|
||||||
if (element == null || e.Pointer.Captured != element || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
if (element == null || !ReferenceEquals(e.Pointer.Captured, element) || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed || ViewModel?.Layer == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
SKPoint position = GetPositionForViewModel(e);
|
||||||
|
ViewModel.UpdateResize(position, GetResizeDirection(element), e.KeyModifiers.HasFlag(KeyModifiers.Shift));
|
||||||
|
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
private SKPoint GetPositionForViewModel(PointerEventArgs e)
|
||||||
|
{
|
||||||
|
if (ViewModel?.Layer == null)
|
||||||
|
return SKPoint.Empty;
|
||||||
|
|
||||||
|
SKPoint point = CounteractLayerRotation(e.GetCurrentPoint(this).Position.ToSKPoint(), ViewModel.Layer);
|
||||||
|
return point + _dragOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SKPoint CounteractLayerRotation(SKPoint point, Layer layer)
|
||||||
|
{
|
||||||
|
SKPoint pivot = layer.GetLayerAnchorPosition();
|
||||||
|
|
||||||
|
using SKPath counterRotatePath = new();
|
||||||
|
counterRotatePath.AddPoly(new[] {SKPoint.Empty, point}, false);
|
||||||
|
counterRotatePath.Transform(SKMatrix.CreateRotationDegrees(layer.Transform.Rotation.CurrentValue * -1, pivot.X, pivot.Y));
|
||||||
|
|
||||||
|
return counterRotatePath.Points[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
private TransformToolViewModel.ResizeSide GetResizeDirection(Shape shape)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(shape, _resizeTopLeft))
|
||||||
|
return TransformToolViewModel.ResizeSide.Top | TransformToolViewModel.ResizeSide.Left;
|
||||||
|
if (ReferenceEquals(shape, _resizeTopRight))
|
||||||
|
return TransformToolViewModel.ResizeSide.Top | TransformToolViewModel.ResizeSide.Right;
|
||||||
|
if (ReferenceEquals(shape, _resizeBottomRight))
|
||||||
|
return TransformToolViewModel.ResizeSide.Bottom | TransformToolViewModel.ResizeSide.Right;
|
||||||
|
if (ReferenceEquals(shape, _resizeBottomLeft))
|
||||||
|
return TransformToolViewModel.ResizeSide.Bottom | TransformToolViewModel.ResizeSide.Left;
|
||||||
|
if (ReferenceEquals(shape, _resizeTopCenter))
|
||||||
|
return TransformToolViewModel.ResizeSide.Top;
|
||||||
|
if (ReferenceEquals(shape, _resizeRightCenter))
|
||||||
|
return TransformToolViewModel.ResizeSide.Right;
|
||||||
|
if (ReferenceEquals(shape, _resizeBottomCenter))
|
||||||
|
return TransformToolViewModel.ResizeSide.Bottom;
|
||||||
|
if (ReferenceEquals(shape, _resizeLeftCenter))
|
||||||
|
return TransformToolViewModel.ResizeSide.Left;
|
||||||
|
|
||||||
|
throw new ArgumentException("Given shape isn't a resize shape");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,9 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.UI.Shared.Extensions;
|
using Artemis.UI.Shared.Extensions;
|
||||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
|
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.Mixins;
|
using Avalonia.Controls.Mixins;
|
||||||
using Material.Icons;
|
using Material.Icons;
|
||||||
@ -14,6 +17,7 @@ namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools;
|
|||||||
|
|
||||||
public class TransformToolViewModel : ToolViewModel
|
public class TransformToolViewModel : ToolViewModel
|
||||||
{
|
{
|
||||||
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
private readonly ObservableAsPropertyHelper<bool> _isEnabled;
|
private readonly ObservableAsPropertyHelper<bool> _isEnabled;
|
||||||
private RelativePoint _relativeAnchor;
|
private RelativePoint _relativeAnchor;
|
||||||
private double _inverseRotation;
|
private double _inverseRotation;
|
||||||
@ -21,10 +25,13 @@ public class TransformToolViewModel : ToolViewModel
|
|||||||
private double _rotation;
|
private double _rotation;
|
||||||
private Rect _shapeBounds;
|
private Rect _shapeBounds;
|
||||||
private Point _anchor;
|
private Point _anchor;
|
||||||
|
private TimeSpan _time;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public TransformToolViewModel(IProfileEditorService profileEditorService)
|
public TransformToolViewModel(IProfileEditorService profileEditorService)
|
||||||
{
|
{
|
||||||
|
_profileEditorService = profileEditorService;
|
||||||
|
|
||||||
// Not disposed when deactivated but when really disposed
|
// Not disposed when deactivated but when really disposed
|
||||||
_isEnabled = profileEditorService.ProfileElement.Select(p => p is Layer).ToProperty(this, vm => vm.IsEnabled);
|
_isEnabled = profileEditorService.ProfileElement.Select(p => p is Layer).ToProperty(this, vm => vm.IsEnabled);
|
||||||
|
|
||||||
@ -69,7 +76,11 @@ public class TransformToolViewModel : ToolViewModel
|
|||||||
.DisposeWith(d);
|
.DisposeWith(d);
|
||||||
|
|
||||||
this.WhenAnyValue(vm => vm.Layer).Subscribe(_ => Update()).DisposeWith(d);
|
this.WhenAnyValue(vm => vm.Layer).Subscribe(_ => Update()).DisposeWith(d);
|
||||||
profileEditorService.Time.Subscribe(_ => Update()).DisposeWith(d);
|
profileEditorService.Time.Subscribe(t =>
|
||||||
|
{
|
||||||
|
_time = t;
|
||||||
|
Update();
|
||||||
|
}).DisposeWith(d);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,17 +162,159 @@ public class TransformToolViewModel : ToolViewModel
|
|||||||
Anchor = new Point(ShapeBounds.Width * RelativeAnchor.Point.X, ShapeBounds.Height * RelativeAnchor.Point.Y);
|
Anchor = new Point(ShapeBounds.Width * RelativeAnchor.Point.X, ShapeBounds.Height * RelativeAnchor.Point.Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ShapeControlPoint
|
#region Resizing
|
||||||
|
|
||||||
|
private SKSize _resizeStartScale;
|
||||||
|
private bool _hadKeyframe;
|
||||||
|
|
||||||
|
public void StartResize(SKPoint position)
|
||||||
{
|
{
|
||||||
TopLeft,
|
if (Layer == null)
|
||||||
TopRight,
|
return;
|
||||||
BottomRight,
|
|
||||||
BottomLeft,
|
_resizeStartScale = Layer.Transform.Scale.CurrentValue;
|
||||||
TopCenter,
|
_hadKeyframe = Layer.Transform.Scale.Keyframes.Any(k => k.Position == _time);
|
||||||
RightCenter,
|
}
|
||||||
BottomCenter,
|
|
||||||
LeftCenter,
|
public void FinishResize(SKPoint position, ResizeSide side, bool evenSides)
|
||||||
LayerShape,
|
{
|
||||||
Anchor
|
if (Layer == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Grab the size one last time
|
||||||
|
SKSize size = UpdateResize(position, side, evenSides);
|
||||||
|
|
||||||
|
// If the layer has keyframes, new keyframes may have been added while the user was dragging
|
||||||
|
if (Layer.Transform.Scale.KeyframesEnabled)
|
||||||
|
{
|
||||||
|
// If there was already a keyframe at the old spot, edit that keyframe
|
||||||
|
if (_hadKeyframe)
|
||||||
|
_profileEditorService.ExecuteCommand(new UpdateLayerProperty<SKSize>(Layer.Transform.Scale, size, _resizeStartScale, _time));
|
||||||
|
// If there was no keyframe yet, remove the keyframe that was created while dragging and create a permanent one
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Layer.Transform.Scale.RemoveKeyframe(Layer.Transform.Scale.Keyframes.First(k => k.Position == _time));
|
||||||
|
_profileEditorService.ExecuteCommand(new UpdateLayerProperty<SKSize>(Layer.Transform.Scale, size, _time));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_profileEditorService.ExecuteCommand(new UpdateLayerProperty<SKSize>(Layer.Transform.Scale, size, _resizeStartScale, _time));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SKSize UpdateResize(SKPoint position, ResizeSide side, bool evenSides)
|
||||||
|
{
|
||||||
|
if (Layer == null)
|
||||||
|
return SKSize.Empty;
|
||||||
|
|
||||||
|
SKPoint normalizedAnchor = Layer.Transform.AnchorPoint;
|
||||||
|
// TODO Remove when anchor is centralized at 0.5,0.5
|
||||||
|
normalizedAnchor = new SKPoint(normalizedAnchor.X + 0.5f, normalizedAnchor.Y + 0.5f);
|
||||||
|
|
||||||
|
// The anchor is used to ensure a side can't shrink past the anchor
|
||||||
|
SKPoint anchor = Layer.GetLayerAnchorPosition();
|
||||||
|
// The bounds are used to determine whether to shrink or grow
|
||||||
|
SKRect shapeBounds = Layer.GetLayerPath(true, true, false).Bounds;
|
||||||
|
|
||||||
|
float width = shapeBounds.Width;
|
||||||
|
float height = shapeBounds.Height;
|
||||||
|
|
||||||
|
// Resize each side as requested, the sides of each axis are mutually exclusive
|
||||||
|
if (side.HasFlag(ResizeSide.Left))
|
||||||
|
{
|
||||||
|
if (position.X > anchor.X)
|
||||||
|
position.X = anchor.X;
|
||||||
|
|
||||||
|
float anchorOffset = 1f - normalizedAnchor.X;
|
||||||
|
float difference = MathF.Abs(shapeBounds.Left - position.X);
|
||||||
|
if (position.X < shapeBounds.Left)
|
||||||
|
width += difference / anchorOffset;
|
||||||
|
else
|
||||||
|
width -= difference / anchorOffset;
|
||||||
|
}
|
||||||
|
else if (side.HasFlag(ResizeSide.Right))
|
||||||
|
{
|
||||||
|
if (position.X < anchor.X)
|
||||||
|
position.X = anchor.X;
|
||||||
|
|
||||||
|
float anchorOffset = normalizedAnchor.X;
|
||||||
|
float difference = MathF.Abs(shapeBounds.Right - position.X);
|
||||||
|
if (position.X > shapeBounds.Right)
|
||||||
|
width += difference / anchorOffset;
|
||||||
|
else
|
||||||
|
width -= difference / anchorOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (side.HasFlag(ResizeSide.Top))
|
||||||
|
{
|
||||||
|
if (position.Y > anchor.Y)
|
||||||
|
position.Y = anchor.Y;
|
||||||
|
|
||||||
|
float anchorOffset = 1f - normalizedAnchor.Y;
|
||||||
|
float difference = MathF.Abs(shapeBounds.Top - position.Y);
|
||||||
|
if (position.Y < shapeBounds.Top)
|
||||||
|
height += difference / anchorOffset;
|
||||||
|
else
|
||||||
|
height -= difference / anchorOffset;
|
||||||
|
}
|
||||||
|
else if (side.HasFlag(ResizeSide.Bottom))
|
||||||
|
{
|
||||||
|
if (position.Y < anchor.Y)
|
||||||
|
position.Y = anchor.Y;
|
||||||
|
|
||||||
|
float anchorOffset = normalizedAnchor.Y;
|
||||||
|
float difference = MathF.Abs(shapeBounds.Bottom - position.Y);
|
||||||
|
if (position.Y > shapeBounds.Bottom)
|
||||||
|
height += difference / anchorOffset;
|
||||||
|
else
|
||||||
|
height -= difference / anchorOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Even out the sides to the size of the longest side
|
||||||
|
if (evenSides)
|
||||||
|
{
|
||||||
|
if (width > height)
|
||||||
|
width = height;
|
||||||
|
else
|
||||||
|
height = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize the scale to a percentage
|
||||||
|
SKRect bounds = Layer.GetLayerBounds();
|
||||||
|
width = width / bounds.Width * 100f;
|
||||||
|
height = height / bounds.Height * 100f;
|
||||||
|
|
||||||
|
Layer.Transform.Scale.SetCurrentValue(new SKSize(width, height), _time);
|
||||||
|
return new SKSize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Rotating
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Movement
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Anchor movement
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum ResizeSide
|
||||||
|
{
|
||||||
|
Top = 1,
|
||||||
|
Right = 2,
|
||||||
|
Bottom = 4,
|
||||||
|
Left = 8,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user