diff --git a/samples/AvalonDraw/MainWindow.axaml b/samples/AvalonDraw/MainWindow.axaml index 850c437742..3848a2276a 100644 --- a/samples/AvalonDraw/MainWindow.axaml +++ b/samples/AvalonDraw/MainWindow.axaml @@ -94,6 +94,8 @@ + + diff --git a/samples/AvalonDraw/MainWindow.axaml.cs b/samples/AvalonDraw/MainWindow.axaml.cs index 23df361bcf..022c08b296 100644 --- a/samples/AvalonDraw/MainWindow.axaml.cs +++ b/samples/AvalonDraw/MainWindow.axaml.cs @@ -135,6 +135,10 @@ private enum DropPosition { None, Inside, Before, After } private SK.SKPoint _rotateStart; private SK.SKPoint _rotateCenter; private float _startAngle; + private bool _isSkewing; + private float _startSkewX; + private float _startSkewY; + private bool _skewMode; private bool _isPanning; private Point _panStart; @@ -682,7 +686,10 @@ private async void SvgView_OnPointerPressed(object? sender, PointerPressedEventA return; } SaveUndoState(); - _isResizing = true; + if (_skewMode) + _isSkewing = true; + else + _isResizing = true; _resizeElement = _selectedElement; _resizeHandle = handle; _resizeStart = new Shim.SKPoint(pp.X, pp.Y); @@ -697,6 +704,7 @@ private async void SvgView_OnPointerPressed(object? sender, PointerPressedEventA _resizeStartLocal = _resizeInverse.MapPoint(_resizeStart); (_startTransX, _startTransY) = _selectionService.GetTranslation(_resizeElement); (_startScaleX, _startScaleY) = _selectionService.GetScale(_resizeElement); + (_startSkewX, _startSkewY) = _selectionService.GetSkew(_resizeElement); e.Pointer.Capture(SvgView); return; } @@ -999,6 +1007,16 @@ private void SvgView_OnPointerMoved(object? sender, PointerEventArgs e) UpdateSelectedDrawable(); SvgView.InvalidateVisual(); } + else if (_isSkewing && _resizeElement is { }) + { + var local = _resizeInverse.MapPoint(new Shim.SKPoint(skp.X, skp.Y)); + var dx = local.X - _resizeStartLocal.X; + var dy = local.Y - _resizeStartLocal.Y; + _selectionService.SkewElement(_resizeElement, _resizeHandle, dx, dy, _startRect, _startSkewX, _startSkewY); + SvgView.SkSvg!.FromSvgDocument(_document); + UpdateSelectedDrawable(); + SvgView.InvalidateVisual(); + } else if (_isRotating && _rotateElement is { }) { var a1 = Math.Atan2(_rotateStart.Y - _rotateCenter.Y, _rotateStart.X - _rotateCenter.X); @@ -1183,6 +1201,15 @@ private void SvgView_OnPointerReleased(object? sender, PointerReleasedEventArgs LoadProperties(_resizeElement); } } + else if (_isSkewing) + { + _isSkewing = false; + if (_resizeElement is { }) + { + SaveUndoState(); + LoadProperties(_resizeElement); + } + } else if (_pathService.ActivePoint >= 0) { _pathService.ActivePoint = -1; @@ -2136,6 +2163,11 @@ private void FiltersMenuItem_Click(object? sender, RoutedEventArgs e) SvgView.InvalidateVisual(); } + private void SkewModeMenuItem_Click(object? sender, RoutedEventArgs e) + { + _skewMode = !_skewMode; + } + private void ResetViewButton_Click(object? sender, RoutedEventArgs e) { SvgView.Zoom = 1.0; @@ -2999,6 +3031,7 @@ private void ToolServiceOnToolChanged(Tool oldTool, Tool newTool) if (_polyEditing && newTool != Tool.PolygonSelect && newTool != Tool.PolylineSelect) StopPolyEditing(); _isResizing = false; + _isSkewing = false; _isRotating = false; SvgView.InvalidateVisual(); } diff --git a/samples/AvalonDraw/Services/SelectionService.cs b/samples/AvalonDraw/Services/SelectionService.cs index a8942efab5..b11e265758 100644 --- a/samples/AvalonDraw/Services/SelectionService.cs +++ b/samples/AvalonDraw/Services/SelectionService.cs @@ -2,12 +2,12 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using SK = SkiaSharp; using Svg; using Svg.Model.Drawables; -using Svg.Transforms; using Svg.Skia; +using Svg.Transforms; using Shim = ShimSkiaSharp; +using SK = SkiaSharp; namespace AvalonDraw.Services; @@ -172,6 +172,32 @@ public void SetScale(SvgVisualElement element, float x, float y) } } + public (float X, float Y) GetSkew(SvgVisualElement? element) + { + if (element?.Transforms is { } t) + { + var sk = t.OfType().FirstOrDefault(); + if (sk is { }) + return (sk.AngleX, sk.AngleY); + } + return (0f, 0f); + } + + public void SetSkew(SvgVisualElement element, float x, float y) + { + element.Transforms ??= new SvgTransformCollection(); + var sk = element.Transforms.OfType().FirstOrDefault(); + if (sk != null) + { + sk.AngleX = x; + sk.AngleY = y; + } + else + { + element.Transforms.Add(new SvgSkew(x, y)); + } + } + public float Snap(float value) { if (!SnapToGrid || GridSize <= 0) @@ -204,14 +230,40 @@ void ResizeBox(dynamic el, int h, float ddx, float ddy) float hgt = el.Height.Value; switch (h) { - case 0: x = startRect.Left + ddx; y = startRect.Top + ddy; w = startRect.Right - x; hgt = startRect.Bottom - y; break; - case 1: y = startRect.Top + ddy; hgt = startRect.Bottom - y; break; - case 2: y = startRect.Top + ddy; w = startRect.Width + ddx; hgt = startRect.Bottom - y; break; - case 3: w = startRect.Width + ddx; break; - case 4: w = startRect.Width + ddx; hgt = startRect.Height + ddy; break; - case 5: hgt = startRect.Height + ddy; break; - case 6: x = startRect.Left + ddx; w = startRect.Right - x; hgt = startRect.Height + ddy; break; - case 7: x = startRect.Left + ddx; w = startRect.Right - x; break; + case 0: + x = startRect.Left + ddx; + y = startRect.Top + ddy; + w = startRect.Right - x; + hgt = startRect.Bottom - y; + break; + case 1: + y = startRect.Top + ddy; + hgt = startRect.Bottom - y; + break; + case 2: + y = startRect.Top + ddy; + w = startRect.Width + ddx; + hgt = startRect.Bottom - y; + break; + case 3: + w = startRect.Width + ddx; + break; + case 4: + w = startRect.Width + ddx; + hgt = startRect.Height + ddy; + break; + case 5: + hgt = startRect.Height + ddy; + break; + case 6: + x = startRect.Left + ddx; + w = startRect.Right - x; + hgt = startRect.Height + ddy; + break; + case 7: + x = startRect.Left + ddx; + w = startRect.Right - x; + break; } if (SnapToGrid) { @@ -234,14 +286,40 @@ void ResizeCircle(SvgCircle c, int h, float ddx, float ddy) float hgt = startRect.Height; switch (h) { - case 0: x += ddx; y += ddy; w = startRect.Right - x; hgt = startRect.Bottom - y; break; - case 1: y += ddy; hgt = startRect.Bottom - y; break; - case 2: y += ddy; w += ddx; hgt = startRect.Bottom - y; break; - case 3: w += ddx; break; - case 4: w += ddx; hgt += ddy; break; - case 5: hgt += ddy; break; - case 6: x += ddx; w = startRect.Right - x; hgt += ddy; break; - case 7: x += ddx; w = startRect.Right - x; break; + case 0: + x += ddx; + y += ddy; + w = startRect.Right - x; + hgt = startRect.Bottom - y; + break; + case 1: + y += ddy; + hgt = startRect.Bottom - y; + break; + case 2: + y += ddy; + w += ddx; + hgt = startRect.Bottom - y; + break; + case 3: + w += ddx; + break; + case 4: + w += ddx; + hgt += ddy; + break; + case 5: + hgt += ddy; + break; + case 6: + x += ddx; + w = startRect.Right - x; + hgt += ddy; + break; + case 7: + x += ddx; + w = startRect.Right - x; + break; } var cx = x + w / 2f; var cy = y + hgt / 2f; @@ -265,20 +343,52 @@ void ResizePath(SvgPath p, int h, float ddx, float ddy) float hgt = startRect.Height; switch (h) { - case 0: x += ddx; y += ddy; w = startRect.Right - x; hgt = startRect.Bottom - y; break; - case 1: y += ddy; hgt = startRect.Bottom - y; break; - case 2: y += ddy; w += ddx; hgt = startRect.Bottom - y; break; - case 3: w += ddx; break; - case 4: w += ddx; hgt += ddy; break; - case 5: hgt += ddy; break; - case 6: x += ddx; w = startRect.Right - x; hgt += ddy; break; - case 7: x += ddx; w = startRect.Right - x; break; + case 0: + x += ddx; + y += ddy; + w = startRect.Right - x; + hgt = startRect.Bottom - y; + break; + case 1: + y += ddy; + hgt = startRect.Bottom - y; + break; + case 2: + y += ddy; + w += ddx; + hgt = startRect.Bottom - y; + break; + case 3: + w += ddx; + break; + case 4: + w += ddx; + hgt += ddy; + break; + case 5: + hgt += ddy; + break; + case 6: + x += ddx; + w = startRect.Right - x; + hgt += ddy; + break; + case 7: + x += ddx; + w = startRect.Right - x; + break; } if (SnapToGrid) { - x = Snap(x); y = Snap(y); w = Snap(w); hgt = Snap(hgt); + x = Snap(x); + y = Snap(y); + w = Snap(w); + hgt = Snap(hgt); } - if (w == 0) w = 0.01f; if (hgt == 0) hgt = 0.01f; + if (w == 0) + w = 0.01f; + if (hgt == 0) + hgt = 0.01f; var sx = w / startRect.Width; var sy = hgt / startRect.Height; var tx = x - startRect.Left; @@ -288,6 +398,28 @@ void ResizePath(SvgPath p, int h, float ddx, float ddy) } } + public void SkewElement(SvgVisualElement element, int handle, float dx, float dy, SK.SKRect startRect, float startSkewX, float startSkewY) + { + var ax = startSkewX; + var ay = startSkewY; + switch (handle) + { + case 1: + case 5: + ax += (float)(Math.Atan(dx / startRect.Height) * 180.0 / Math.PI); + break; + case 3: + case 7: + ay += (float)(Math.Atan(dy / startRect.Width) * 180.0 / Math.PI); + break; + default: + ax += (float)(Math.Atan(dx / startRect.Height) * 180.0 / Math.PI); + ay += (float)(Math.Atan(dy / startRect.Width) * 180.0 / Math.PI); + break; + } + SetSkew(element, ax, ay); + } + public bool GetDragProperties(SvgVisualElement element, out List<(PropertyInfo Prop, SvgUnit Unit, char Axis)> props) { var list = new List<(PropertyInfo Prop, SvgUnit Unit, char Axis)>();