Sfoglia il codice sorgente

selected control points and fixed handle event handling

flabbet 9 mesi fa
parent
commit
c6a81dfd05

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit b3636b77962f9bb7c6263391a51033fde1329742
+Subproject commit 794ad981e39dc144f73cb9b494ec3b44210a02b7

+ 1 - 0
src/PixiEditor/Styles/PixiEditor.Handles.axaml

@@ -61,6 +61,7 @@
 
             <system:String x:Key="RotateHandle">M -1.26 -0.455 Q 0 0.175 1.26 -0.455 L 1.12 -0.735 L 2.1 -0.7 L 1.54 0.105 L 1.4 -0.175 Q 0 0.525 -1.4 -0.175 L -1.54 0.105 L -2.1 -0.7 L -1.12 -0.735 Z</system:String>
             <SolidColorBrush x:Key="HandleBrush" Color="{DynamicResource GlyphColor}"/>
+            <SolidColorBrush x:Key="SelectedHandleBrush" Color="{DynamicResource ThemeAccent2Color}"/>
             <SolidColorBrush x:Key="HandleBackgroundBrush" Color="{DynamicResource GlyphBackground}"/>
 
             <system:Double x:Key="HandleSize">20</system:Double>

+ 9 - 1
src/PixiEditor/Views/Overlays/Handles/AnchorHandle.cs

@@ -11,18 +11,26 @@ namespace PixiEditor.Views.Overlays.Handles;
 public class AnchorHandle : RectangleHandle
 {
     private Paint paint;
+    private Paint selectedPaint;
+    
+    public bool IsSelected { get; set; } = false;
+    
     public AnchorHandle(Overlay owner) : base(owner)
     {
         Size = new VecD(GetResource<double>("AnchorHandleSize"));
         paint = GetPaint("HandleBrush");
+        selectedPaint = GetPaint("SelectedHandleBrush");
         StrokePaint = paint;
-        StrokePaint.Style = PaintStyle.Stroke;
     }
 
 
     public override void Draw(Canvas context)
     {
         paint.StrokeWidth = (float)(1.0 / ZoomScale);
+        selectedPaint.StrokeWidth = (float)(2.5 / ZoomScale);
+        
+        StrokePaint = IsSelected ? selectedPaint : paint;
+        StrokePaint.Style = PaintStyle.Stroke;
         base.Draw(context);
     }
 }

+ 30 - 14
src/PixiEditor/Views/Overlays/Handles/Handle.cs

@@ -16,7 +16,7 @@ using Path = Avalonia.Controls.Shapes.Path;
 
 namespace PixiEditor.Views.Overlays.Handles;
 
-public delegate void HandleEvent(Handle source, VecD position);
+public delegate void HandleEvent(Handle source, OverlayPointerArgs args);
 
 public abstract class Handle : IHandle
 {
@@ -30,10 +30,10 @@ public abstract class Handle : IHandle
 
     public event HandleEvent OnPress;
     public event HandleEvent OnDrag;
-    public event Action<Handle> OnRelease;
-    public event Action<Handle> OnHover;
-    public event Action<Handle> OnExit;
-    public event Action<Handle> OnTap;
+    public event HandleEvent OnRelease;
+    public event HandleEvent OnHover;
+    public event HandleEvent OnExit;
+    public event HandleEvent OnTap;
     public Cursor? Cursor { get; set; }
 
     private bool isPressed;
@@ -91,6 +91,11 @@ public abstract class Handle : IHandle
         {
             return;
         }
+        
+        if(args.Handled)
+        {
+            return;
+        }
 
         VecD handlePos = Position;
 
@@ -99,7 +104,7 @@ public abstract class Handle : IHandle
             args.Handled = true;
             OnPressed(args);
             moved = false;
-            OnPress?.Invoke(this, args.Point);
+            OnPress?.Invoke(this, args);
             isPressed = true;
             args.Pointer.Capture(Owner);
         }
@@ -108,7 +113,12 @@ public abstract class Handle : IHandle
     protected virtual void OnPointerMoved(OverlayPointerArgs args)
     {
         VecD handlePos = Position;
-
+        
+        if(args.Handled)
+        {
+            return;
+        }
+        
         bool isWithinHandle = IsWithinHandle(handlePos, args.Point, ZoomScale);
 
         if (!isHovered && isWithinHandle)
@@ -119,13 +129,13 @@ public abstract class Handle : IHandle
                 Owner.Cursor = Cursor;
             }
 
-            OnHover?.Invoke(this);
+            OnHover?.Invoke(this, args);
         }
         else if (isHovered && !isWithinHandle)
         {
             isHovered = false;
             Owner.Cursor = null;
-            OnExit?.Invoke(this);
+            OnExit?.Invoke(this, args);
         }
 
         if (!isPressed)
@@ -133,7 +143,8 @@ public abstract class Handle : IHandle
             return;
         }
 
-        OnDrag?.Invoke(this, args.Point);
+        OnDrag?.Invoke(this, args);
+        args.Handled = true;
         moved = true;
     }
 
@@ -143,18 +154,23 @@ public abstract class Handle : IHandle
         {
             return;
         }
+        
+        if(args.Handled)
+        {
+            return;
+        }
 
         if (isPressed)
         {
             isPressed = false;
             if (!moved)
             {
-                OnTap?.Invoke(this);
+                OnTap?.Invoke(this, args);
             }
-            
-            OnRelease?.Invoke(this);
+
+            OnRelease?.Invoke(this, args);
             args.Pointer.Capture(null);
-            
+            args.Handled = true;
         }
     }
 }

+ 12 - 12
src/PixiEditor/Views/Overlays/LineToolOverlay/LineToolOverlay.cs

@@ -114,7 +114,7 @@ internal class LineToolOverlay : Overlay
         startHandle = new AnchorHandle(this);
         startHandle.StrokePaint = blackPaint;
         startHandle.OnDrag += StartHandleOnDrag;
-        startHandle.OnHover += handle => Refresh();
+        startHandle.OnHover += (handle, _) => Refresh();
         startHandle.OnRelease += OnHandleRelease;
         startHandle.Cursor = new Cursor(StandardCursorType.Arrow);
         AddHandle(startHandle);
@@ -123,7 +123,7 @@ internal class LineToolOverlay : Overlay
         endHandle.StrokePaint = blackPaint;
         endHandle.OnDrag += EndHandleOnDrag;
         endHandle.Cursor = new Cursor(StandardCursorType.Arrow);
-        endHandle.OnHover += handle => Refresh();
+        endHandle.OnHover += (handle, _) => Refresh();
         endHandle.OnRelease += OnHandleRelease;
         AddHandle(endHandle);
 
@@ -131,7 +131,7 @@ internal class LineToolOverlay : Overlay
         moveHandle.StrokePaint = blackPaint;
         moveHandle.OnDrag += MoveHandleOnDrag;
         endHandle.Cursor = new Cursor(StandardCursorType.Arrow);
-        moveHandle.OnHover += handle => Refresh();
+        moveHandle.OnHover += (handle, _)=> Refresh();
         moveHandle.OnRelease += OnHandleRelease;
         AddHandle(moveHandle);
 
@@ -144,7 +144,7 @@ internal class LineToolOverlay : Overlay
         lastMousePos = args.Point;
     }
 
-    private void OnHandleRelease(Handle obj)
+    private void OnHandleRelease(Handle obj, OverlayPointerArgs args)
     {
         if (SnappingController != null)
         {
@@ -206,27 +206,27 @@ internal class LineToolOverlay : Overlay
         args.Pointer.Capture(this);
     }
 
-    private void StartHandleOnDrag(Handle source, VecD position)
+    private void StartHandleOnDrag(Handle source, OverlayPointerArgs args)
     {
-        VecD delta = position - mouseDownPos;
+        VecD delta = args.Point - mouseDownPos;
         LineStart = SnapAndHighlight(lineStartOnMouseDown + delta);
         movedWhileMouseDown = true;
 
-        lastMousePos = position;
+        lastMousePos = args.Point;
         isDraggingHandle = true;
         IsSizeBoxEnabled = true;
     }
 
-    private void EndHandleOnDrag(Handle source, VecD position)
+    private void EndHandleOnDrag(Handle source, OverlayPointerArgs args)
     {
-        VecD delta = position - mouseDownPos;
+        VecD delta = args.Point - mouseDownPos;
         VecD final = SnapAndHighlight(lineEndOnMouseDown + delta);
 
         LineEnd = final;
         movedWhileMouseDown = true;
 
         isDraggingHandle = true;
-        lastMousePos = position;
+        lastMousePos = args.Point;
         IsSizeBoxEnabled = true;
     }
 
@@ -255,9 +255,9 @@ internal class LineToolOverlay : Overlay
         return final;
     }
 
-    private void MoveHandleOnDrag(Handle source, VecD position)
+    private void MoveHandleOnDrag(Handle source, OverlayPointerArgs args)
     {
-        var delta = position - mouseDownPos;
+        var delta = args.Point - mouseDownPos;
 
         VecD mappedStart = lineStartOnMouseDown;
         VecD mappedEnd = lineEndOnMouseDown;

+ 24 - 6
src/PixiEditor/Views/Overlays/Overlay.cs

@@ -66,34 +66,52 @@ public abstract class Overlay : Decorator, IOverlay // TODO: Maybe make it not a
         InvalidateVisual(); // For elements in visual tree
     }
 
+    // Logically, subscribers (handles) of these events are rendered in 0 to n order, so the last one is rendered on top
+    // that's why reversing invocation order makes pointer events be handled in the order of rendering,
+    // which is more intuitive for the user since the topmost element should be the one to handle the event first
+    
     public void EnterPointer(OverlayPointerArgs args)
     {
         OnOverlayPointerEntered(args);
-        PointerEnteredOverlay?.Invoke(args);
+        ReverseInvoke(PointerEnteredOverlay, args);
     }
 
     public void ExitPointer(OverlayPointerArgs args)
     {
         OnOverlayPointerExited(args);
-        PointerExitedOverlay?.Invoke(args);
+        ReverseInvoke(PointerExitedOverlay, args);
     }
 
     public void MovePointer(OverlayPointerArgs args)
     {
         OnOverlayPointerMoved(args);
-        PointerMovedOverlay?.Invoke(args);
+        ReverseInvoke(PointerMovedOverlay, args);
     }
 
     public void PressPointer(OverlayPointerArgs args)
     {
         OnOverlayPointerPressed(args);
-        PointerPressedOverlay?.Invoke(args);
+        ReverseInvoke(PointerPressedOverlay, args);
     }
 
     public void ReleasePointer(OverlayPointerArgs args)
     {
         OnOverlayPointerReleased(args);
-        PointerReleasedOverlay?.Invoke(args);
+        ReverseInvoke(PointerReleasedOverlay, args);
+    }
+    
+    
+    private void ReverseInvoke(PointerEvent? pointerEvent, OverlayPointerArgs args)
+    {
+        if(pointerEvent == null) return;
+        
+        var invokeList = pointerEvent.GetInvocationList();
+        
+        for (int i = invokeList.Length - 1; i >= 0; i--)
+        {
+            var handler = (PointerEvent)invokeList[i];
+            handler.Invoke(args);
+        }
     }
 
     public virtual bool TestHit(VecD point)
@@ -106,7 +124,7 @@ public abstract class Overlay : Decorator, IOverlay // TODO: Maybe make it not a
         if (Handles.Contains(handle)) return;
 
         Handles.Add(handle);
-        handle.ZoomScale = ZoomScale; 
+        handle.ZoomScale = ZoomScale;
     }
 
     public void ForAllHandles(Action<Handle> action)

+ 75 - 33
src/PixiEditor/Views/Overlays/PathOverlay/VectorPathOverlay.cs

@@ -1,9 +1,8 @@
 using System.Windows.Input;
 using Avalonia;
-using Drawie.Backend.Core.ColorsImpl;
-using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Vector;
 using Drawie.Numerics;
+using PixiEditor.Extensions.UI.Overlays;
 using PixiEditor.Views.Overlays.Drawables;
 using PixiEditor.Views.Overlays.Handles;
 using Canvas = Drawie.Backend.Core.Surfaces.Canvas;
@@ -22,8 +21,9 @@ public class VectorPathOverlay : Overlay
         set => SetValue(PathProperty, value);
     }
 
-    public static readonly StyledProperty<ICommand> AddToUndoCommandProperty = AvaloniaProperty.Register<VectorPathOverlay, ICommand>(
-        nameof(AddToUndoCommand));
+    public static readonly StyledProperty<ICommand> AddToUndoCommandProperty =
+        AvaloniaProperty.Register<VectorPathOverlay, ICommand>(
+            nameof(AddToUndoCommand));
 
     public ICommand AddToUndoCommand
     {
@@ -34,7 +34,7 @@ public class VectorPathOverlay : Overlay
     private DashedStroke dashedStroke = new DashedStroke();
 
     private List<AnchorHandle> pointsHandles = new List<AnchorHandle>();
-    
+
     static VectorPathOverlay()
     {
         AffectsOverlayRender(PathProperty);
@@ -63,6 +63,8 @@ public class VectorPathOverlay : Overlay
     {
         for (int i = 0; i < points.Count; i++)
         {
+            pointsHandles[i].IsSelected = i == points.Count - 1;
+
             pointsHandles[i].Position = new VecD(points[i].X, points[i].Y);
             pointsHandles[i].Draw(context);
         }
@@ -82,7 +84,8 @@ public class VectorPathOverlay : Overlay
                 for (int i = pointsHandles.Count; i < points.Count; i++)
                 {
                     var handle = new AnchorHandle(this);
-                    handle.OnDrag += HandleOnOnDrag;
+                    handle.OnPress += OnHandlePress;
+                    handle.OnDrag += OnHandleDrag;
                     handle.OnRelease += OnHandleRelease;
                     handle.OnTap += OnHandleTap;
                     pointsHandles.Add(handle);
@@ -92,21 +95,14 @@ public class VectorPathOverlay : Overlay
         }
     }
 
-    private void OnHandleTap(Handle handle)
+    private void OnHandleTap(Handle handle, OverlayPointerArgs args)
     {
         VectorPath newPath = new VectorPath(Path);
-
         if (IsLastHandle(handle)) return;
 
-        if (IsFirstHandle(handle))
-        {
-            newPath.Close();
-        }
-        else
-        {
-            VecD pos = handle.Position;
-            newPath.LineTo(new VecF((float)pos.X, (float)pos.Y));
-        }
+
+        VecD pos = handle.Position;
+        newPath.LineTo(new VecF((float)pos.X, (float)pos.Y));
 
         Path = newPath;
     }
@@ -115,39 +111,85 @@ public class VectorPathOverlay : Overlay
     {
         return pointsHandles.IndexOf((AnchorHandle)handle) == 0;
     }
-    
+
     private bool IsLastHandle(Handle handle)
     {
         return pointsHandles.IndexOf((AnchorHandle)handle) == pointsHandles.Count - 1;
     }
 
-    private void HandleOnOnDrag(Handle source, VecD position)
+    private void OnHandleDrag(Handle source, OverlayPointerArgs args)
     {
         var handle = (AnchorHandle)source;
         var index = pointsHandles.IndexOf(handle);
-        VecF[] updatedPoints = Path.Points.ToArray();
-        updatedPoints[index] = new VecF((float)position.X, (float)position.Y);
         VectorPath newPath = new VectorPath();
 
-        newPath.MoveTo(updatedPoints[0]);
-
-        for (var i = 1; i < updatedPoints.Length; i++)
+        bool pointHandled = false;
+        int i = 0;
+        foreach (var data in Path)
         {
-            var point = updatedPoints[i];
-            newPath.LineTo(point);
+            VecF point;
+            switch (data.verb)
+            {
+                case PathVerb.Move:
+                    point = data.points[0];
+                    if (i == index)
+                    {
+                        point = (VecF)args.Point;
+                    }
+
+                    newPath.MoveTo(point);
+                    i++;
+                    break;
+                case PathVerb.Line:
+                    point = data.points[1];
+                    if (i == index)
+                    {
+                        point = (VecF)args.Point;
+                    }
+
+                    newPath.LineTo(point);
+                    i++;
+                    break;
+                /*case PathVerb.Quad:
+                    newPath.QuadTo(data.points[0], point);
+                    break;
+                case PathVerb.Conic:
+                    newPath.ConicTo(data.points[0], point, data.points[2].X);
+                    break;
+                case PathVerb.Cubic:
+                    newPath.CubicTo(data.points[0], data.points[1], point);
+                    break;*/
+                case PathVerb.Close:
+                    newPath.Close();
+                    break;
+                case PathVerb.Done:
+                    break;
+                default:
+                    throw new ArgumentOutOfRangeException();
+            }
         }
 
-        using var iterator = Path.CreateIterator(false);
-        if (iterator.IsCloseContour)
+        Path = newPath;
+    }
+
+    private void OnHandlePress(Handle source, OverlayPointerArgs args)
+    {
+        if (args.Modifiers.HasFlag(Avalonia.Input.KeyModifiers.Control))
         {
-            newPath.Close();
-        }
+            if (!IsLastHandle(source))
+            {
+                VectorPath newPath = new VectorPath(Path);
+                newPath.MoveTo(new VecF((float)source.Position.X, (float)source.Position.Y));
 
-        Path = newPath;
+                Path = newPath;
+            }
+        }
     }
-    
-    private void OnHandleRelease(Handle source)
+
+    private void OnHandleRelease(Handle source, OverlayPointerArgs args)
     {
         AddToUndoCommand.Execute(Path);
+
+        Refresh();
     }
 }

+ 5 - 5
src/PixiEditor/Views/Overlays/TransformOverlay/TransformOverlay.cs

@@ -446,7 +446,7 @@ internal class TransformOverlay : Overlay
         return degrees;
     }
 
-    private void OnAnchorHandlePressed(Handle source, VecD position)
+    private void OnAnchorHandlePressed(Handle source, OverlayPointerArgs args)
     {
         capturedAnchor = anchorMap[source];
         cornersOnStartAnchorDrag = Corners;
@@ -461,9 +461,9 @@ internal class TransformOverlay : Overlay
         }
     }
 
-    private void OnMoveHandlePressed(Handle source, VecD position)
+    private void OnMoveHandlePressed(Handle source, OverlayPointerArgs args)
     {
-        StartMoving(position);
+        StartMoving(args.Point);
     }
 
     protected override void OnOverlayPointerExited(OverlayPointerArgs args)
@@ -580,7 +580,7 @@ internal class TransformOverlay : Overlay
         return base.TestHit(point) || Corners.AsScaled(1.25f).IsPointInside(point);
     }
 
-    private void OnMoveHandleReleased(Handle obj)
+    private void OnMoveHandleReleased(Handle obj, OverlayPointerArgs args)
     {
         StopMoving();
     }
@@ -1043,7 +1043,7 @@ internal class TransformOverlay : Overlay
         return originOnStartAnchorDrag + pos - mousePosOnStartAnchorDrag;
     }
 
-    private void OnAnchorHandleReleased(Handle source)
+    private void OnAnchorHandleReleased(Handle source, OverlayPointerArgs args)
     {
         capturedAnchor = null;