Browse Source

Symmetry overlay

Equbuxu 3 years ago
parent
commit
84d7d802fa

+ 1 - 1
src/PixiEditorPrototype/CustomControls/SelectionOverlay.cs

@@ -7,7 +7,7 @@ using SkiaSharp;
 
 
 namespace PixiEditorPrototype.CustomControls;
 namespace PixiEditorPrototype.CustomControls;
 
 
-public class SelectionOverlay : Control
+internal class SelectionOverlay : Control
 {
 {
     public static readonly DependencyProperty PathProperty =
     public static readonly DependencyProperty PathProperty =
         DependencyProperty.Register(nameof(Path), typeof(SKPath), typeof(SelectionOverlay),
         DependencyProperty.Register(nameof(Path), typeof(SKPath), typeof(SelectionOverlay),

+ 5 - 0
src/PixiEditorPrototype/CustomControls/SymmertyOverlay/SymmetryDirection.cs

@@ -0,0 +1,5 @@
+namespace PixiEditorPrototype.CustomControls.SymmertyOverlay;
+internal enum SymmetryDirection
+{
+    Horizontal, Vertical
+}

+ 149 - 0
src/PixiEditorPrototype/CustomControls/SymmertyOverlay/SymmetryOverlay.cs

@@ -0,0 +1,149 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+using ChunkyImageLib.DataHolders;
+
+namespace PixiEditorPrototype.CustomControls.SymmertyOverlay;
+
+internal class SymmetryOverlay : Control
+{
+    public static readonly DependencyProperty HorizontalPositionProperty =
+        DependencyProperty.Register(nameof(HorizontalPosition), typeof(int), typeof(SymmetryOverlay),
+            new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsRender));
+
+    public int HorizontalPosition
+    {
+        get => (int)GetValue(HorizontalPositionProperty);
+        set => SetValue(HorizontalPositionProperty, value);
+    }
+
+    public static readonly DependencyProperty VerticalPositionProperty =
+        DependencyProperty.Register(nameof(VerticalPosition), typeof(int), typeof(SymmetryOverlay),
+            new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsRender));
+
+    public int VerticalPosition
+    {
+        get => (int)GetValue(VerticalPositionProperty);
+        set => SetValue(VerticalPositionProperty, value);
+    }
+
+    public static readonly DependencyProperty HorizontalVisibleProperty =
+        DependencyProperty.Register(nameof(HorizontalVisible), typeof(bool), typeof(SymmetryOverlay),
+            new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
+
+    public bool HorizontalVisible
+    {
+        get => (bool)GetValue(HorizontalVisibleProperty);
+        set => SetValue(HorizontalVisibleProperty, value);
+    }
+
+    public static readonly DependencyProperty VerticalVisibleProperty =
+        DependencyProperty.Register(nameof(VerticalVisible), typeof(bool), typeof(SymmetryOverlay),
+            new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
+
+    public bool VerticalVisible
+    {
+        get => (bool)GetValue(VerticalVisibleProperty);
+        set => SetValue(VerticalVisibleProperty, value);
+    }
+
+    public static readonly DependencyProperty ZoomboxScaleProperty =
+        DependencyProperty.Register(nameof(ZoomboxScale), typeof(double), typeof(SymmetryOverlay),
+            new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsRender));
+
+    public double ZoomboxScale
+    {
+        get => (double)GetValue(ZoomboxScaleProperty);
+        set => SetValue(ZoomboxScaleProperty, value);
+    }
+
+    private const double HandleSize = 16;
+    private Pen borderPen = new Pen(Brushes.Black, 1.0);
+
+    protected override void OnRender(DrawingContext drawingContext)
+    {
+        base.OnRender(drawingContext);
+        if (!HorizontalVisible && !VerticalVisible)
+            return;
+
+        borderPen.Thickness = 1.0 / ZoomboxScale;
+        double radius = HandleSize / ZoomboxScale / 2;
+
+        if (HorizontalVisible)
+        {
+            drawingContext.DrawEllipse(Brushes.White, borderPen, new(-radius, HorizontalPosition), radius, radius);
+            drawingContext.DrawEllipse(Brushes.White, borderPen, new(ActualWidth + radius, HorizontalPosition), radius, radius);
+            drawingContext.DrawLine(borderPen, new(0, HorizontalPosition), new(ActualWidth, HorizontalPosition));
+        }
+        if (VerticalVisible)
+        {
+            drawingContext.DrawEllipse(Brushes.White, borderPen, new(VerticalPosition, -radius), radius, radius);
+            drawingContext.DrawEllipse(Brushes.White, borderPen, new(VerticalPosition, ActualHeight + radius), radius, radius);
+            drawingContext.DrawLine(borderPen, new(VerticalPosition, 0), new(VerticalPosition, ActualHeight));
+        }
+    }
+
+    protected override HitTestResult? HitTestCore(PointHitTestParameters hitTestParameters)
+    {
+        // prevent the line from blocking mouse input
+        if (IsTouchingHandle(ToVector2d(hitTestParameters.HitPoint)) is not null)
+            return new PointHitTestResult(this, hitTestParameters.HitPoint);
+        return null;
+    }
+
+    private SymmetryDirection? IsTouchingHandle(Vector2d position)
+    {
+        double radius = HandleSize / ZoomboxScale / 2;
+        Vector2d left = new(-radius, HorizontalPosition);
+        Vector2d right = new(ActualWidth + radius, HorizontalPosition);
+        Vector2d up = new(VerticalPosition, -radius);
+        Vector2d down = new(VerticalPosition, ActualHeight + radius);
+
+        if (HorizontalVisible && ((left - position).Length < radius || (right - position).Length < radius))
+            return SymmetryDirection.Horizontal;
+        if (VerticalVisible && ((up - position).Length < radius || (down - position).Length < radius))
+            return SymmetryDirection.Vertical;
+        return null;
+    }
+
+    private Vector2d ToVector2d(Point pos) => new Vector2d(pos.X, pos.Y);
+
+    public SymmetryDirection? capturedDirection;
+
+    protected override void OnMouseDown(MouseButtonEventArgs e)
+    {
+        base.OnMouseDown(e);
+
+        var pos = ToVector2d(e.GetPosition(this));
+        var dir = IsTouchingHandle(pos);
+        if (dir is null)
+            return;
+        capturedDirection = dir.Value;
+        CaptureMouse();
+    }
+
+    protected override void OnMouseUp(MouseButtonEventArgs e)
+    {
+        base.OnMouseUp(e);
+
+        if (capturedDirection is null)
+            return;
+        capturedDirection = null;
+        ReleaseMouseCapture();
+    }
+
+    protected override void OnMouseMove(MouseEventArgs e)
+    {
+        base.OnMouseMove(e);
+
+        if (capturedDirection is null)
+            return;
+        var pos = ToVector2d(e.GetPosition(this));
+        if (capturedDirection == SymmetryDirection.Horizontal)
+            HorizontalPosition = (int)Math.Round(Math.Clamp(pos.Y, 0, ActualHeight));
+        if (capturedDirection == SymmetryDirection.Vertical)
+            VerticalPosition = (int)Math.Round(Math.Clamp(pos.X, 0, ActualWidth));
+    }
+}

+ 2 - 0
src/PixiEditorPrototype/UserControls/Viewport/Viewport.xaml

@@ -8,6 +8,7 @@
              xmlns:zoombox="clr-namespace:PixiEditor.Zoombox;assembly=PixiEditor.Zoombox"
              xmlns:zoombox="clr-namespace:PixiEditor.Zoombox;assembly=PixiEditor.Zoombox"
              xmlns:to="clr-namespace:PixiEditorPrototype.CustomControls.TransformOverlay"
              xmlns:to="clr-namespace:PixiEditorPrototype.CustomControls.TransformOverlay"
              xmlns:cust="clr-namespace:PixiEditorPrototype.CustomControls"
              xmlns:cust="clr-namespace:PixiEditorPrototype.CustomControls"
+             xmlns:sym="clr-namespace:PixiEditorPrototype.CustomControls.SymmertyOverlay"
              xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
              xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
              xmlns:conv="clr-namespace:PixiEditorPrototype.Converters"
              xmlns:conv="clr-namespace:PixiEditorPrototype.Converters"
              mc:Ignorable="d"
              mc:Ignorable="d"
@@ -44,6 +45,7 @@
                             </i:EventTrigger>
                             </i:EventTrigger>
                         </i:Interaction.Triggers>
                         </i:Interaction.Triggers>
                     </Image>
                     </Image>
+                    <sym:SymmetryOverlay HorizontalVisible="True" VerticalVisible="True" ZoomboxScale="{Binding Zoombox.Scale}"/>
                     <cust:SelectionOverlay Path="{Binding Document.SelectionPath}" ZoomboxScale="{Binding Zoombox.Scale}"/>
                     <cust:SelectionOverlay Path="{Binding Document.SelectionPath}" ZoomboxScale="{Binding Zoombox.Scale}"/>
                     <to:TransformOverlay HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                     <to:TransformOverlay HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                                             Visibility="{Binding Document.TransformViewModel.TransformActive, Converter={StaticResource BoolToVisibilityConverter}}"
                                             Visibility="{Binding Document.TransformViewModel.TransformActive, Converter={StaticResource BoolToVisibilityConverter}}"