Browse Source

Handles support

Krzysztof Krysiński 1 năm trước cách đây
mục cha
commit
3f5377d437

+ 0 - 13
src/PixiEditor.AvaloniaUI/Views/Main/ViewportControls/Viewport.axaml

@@ -208,19 +208,6 @@
             FlowDirection="LeftToRight" />
         <!--<Grid ZIndex="5" DataContext="{Binding ElementName=vpUc}"
               RenderTransformOrigin="0, 0" RenderTransform="{Binding #zoombox.CanvasTransform}">
-            <symmetryOverlay:SymmetryOverlay
-                Focusable="False"
-                Size="{Binding Document.SizeBindable, Mode=OneWay}"
-                IsHitTestVisible="{Binding ZoomMode, Converter={converters:ZoomModeToHitTestVisibleConverter}}"
-                ZoomboxScale="{Binding #zoombox.Scale}"
-                HorizontalAxisVisible="{Binding Document.HorizontalSymmetryAxisEnabledBindable}"
-                VerticalAxisVisible="{Binding Document.VerticalSymmetryAxisEnabledBindable}"
-                HorizontalAxisY="{Binding Document.HorizontalSymmetryAxisYBindable, Mode=OneWay}"
-                VerticalAxisX="{Binding Document.VerticalSymmetryAxisXBindable, Mode=OneWay}"
-                DragCommand="{xaml:Command PixiEditor.Document.DragSymmetry, UseProvided=True}"
-                DragEndCommand="{xaml:Command PixiEditor.Document.EndDragSymmetry, UseProvided=True}"
-                DragStartCommand="{xaml:Command PixiEditor.Document.StartDragSymmetry, UseProvided=True}"
-                FlowDirection="LeftToRight" />
             <transformOverlay:TransformOverlay
                 Focusable="False"
                 Cursor="Arrow"

+ 2 - 100
src/PixiEditor.AvaloniaUI/Views/Main/ViewportControls/Viewport.axaml.cs

@@ -295,10 +295,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
     public Guid GuidValue { get; } = Guid.NewGuid();
 
     private MouseUpdateController? mouseUpdateController;
-
-    private GridLines gridLinesOverlay;
-    private SelectionOverlay selectionOverlay;
-    private SymmetryOverlay symmetryOverlay;
+    private ViewportOverlays builtInOverlays = new();
 
     static Viewport()
     {
@@ -311,7 +308,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
     {
         InitializeComponent();
 
-        InitBuiltInOverlays();
+        builtInOverlays.Init(this);
         MainImage!.Loaded += OnImageLoaded;
         MainImage.SizeChanged += OnMainImageSizeChanged;
         Loaded += OnLoad;
@@ -322,21 +319,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         viewportGrid.AddHandler(PointerPressedEvent, Image_MouseDown, RoutingStrategies.Bubble);
     }
 
-    private void InitBuiltInOverlays()
-    {
-        gridLinesOverlay = new GridLines();
-        BindGridLines();
-
-        selectionOverlay = new SelectionOverlay();
-        BindSelectionOverlay();
-
-        symmetryOverlay = new SymmetryOverlay();
-        BindSymmetryOverlay();
 
-        ActiveOverlays.Add(gridLinesOverlay);
-        ActiveOverlays.Add(selectionOverlay);
-        ActiveOverlays.Add(symmetryOverlay);
-    }
 
     public Panel? MainImage => zoombox != null ? (Panel?)((Grid?)((Border?)zoombox.AdditionalContent)?.Child)?.Children[0] : null;
     public Scene Scene => scene;
@@ -387,87 +370,6 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         newDoc?.Operations.AddOrUpdateViewport(viewport.GetLocation());
     }
 
-    private void BindGridLines()
-    {
-        Binding isVisBinding = new()
-        {
-            Source = this,
-            Path = "GridLinesVisible",
-            Mode = BindingMode.OneWay
-        };
-
-        Binding binding = new()
-        {
-            Source = Document,
-            Path = "Width",
-            Mode = BindingMode.OneWay
-        };
-
-        gridLinesOverlay.Bind(GridLines.PixelWidthProperty, binding);
-        gridLinesOverlay.Bind(GridLines.ColumnsProperty, binding);
-
-        binding = new Binding
-        {
-            Source = Document,
-            Path = "Height",
-            Mode = BindingMode.OneWay
-        };
-
-        gridLinesOverlay.Bind(GridLines.PixelHeightProperty, binding);
-        gridLinesOverlay.Bind(GridLines.RowsProperty, binding);
-        gridLinesOverlay.Bind(GridLines.IsVisibleProperty, isVisBinding);
-    }
-
-    private void BindSelectionOverlay()
-    {
-        Binding showFillBinding = new()
-        {
-            Source = this,
-            Path = "Document.ToolsSubViewModel.ActiveTool",
-            Converter = new IsSelectionToolConverter(),
-            Mode = BindingMode.OneWay
-        };
-
-        Binding pathBinding = new()
-        {
-            Source = this,
-            Path = "Document.SelectionPathBindable",
-            Mode = BindingMode.OneWay
-        };
-
-        Binding isVisibleBinding = new()
-        {
-            Source = this,
-            Path = "Document.SelectionPathBindable",
-            Mode = BindingMode.OneWay,
-            Converter = new NotNullToVisibilityConverter()
-        };
-
-        selectionOverlay.Bind(SelectionOverlay.ShowFillProperty, showFillBinding);
-        selectionOverlay.Bind(SelectionOverlay.PathProperty, pathBinding);
-        selectionOverlay.Bind(IsVisibleProperty, isVisibleBinding);
-    }
-
-    private void BindSymmetryOverlay()
-    {
-        Binding sizeBinding = new() { Source = this, Path = "Document.SizeBindable", Mode = BindingMode.OneWay };
-        Binding isHitTestVisibleBinding = new() { Source = this, Path = "ZoomMode", Converter = new ZoomModeToHitTestVisibleConverter(), Mode = BindingMode.OneWay };
-        Binding horizontalAxisVisibleBinding = new() { Source = this, Path = "Document.HorizontalSymmetryAxisEnabledBindable", Mode = BindingMode.OneWay };
-        Binding verticalAxisVisibleBinding = new() { Source = this, Path = "Document.VerticalSymmetryAxisEnabledBindable", Mode = BindingMode.OneWay };
-        Binding horizontalAxisYBinding = new() { Source = this, Path = "Document.HorizontalSymmetryAxisYBindable", Mode = BindingMode.OneWay };
-        Binding verticalAxisXBinding = new() { Source = this, Path = "Document.VerticalSymmetryAxisXBindable", Mode = BindingMode.OneWay };
-
-        symmetryOverlay.Bind(SymmetryOverlay.SizeProperty, sizeBinding);
-        symmetryOverlay.Bind(IsHitTestVisibleProperty, isHitTestVisibleBinding);
-        symmetryOverlay.Bind(SymmetryOverlay.HorizontalAxisVisibleProperty, horizontalAxisVisibleBinding);
-        symmetryOverlay.Bind(SymmetryOverlay.VerticalAxisVisibleProperty, verticalAxisVisibleBinding);
-        symmetryOverlay.Bind(SymmetryOverlay.HorizontalAxisYProperty, horizontalAxisYBinding);
-        symmetryOverlay.Bind(SymmetryOverlay.VerticalAxisXProperty, verticalAxisXBinding);
-        symmetryOverlay.DragCommand = (ICommand)new Command("PixiEditor.Document.DragSymmetry") { UseProvided = true }.ProvideValue(null);
-        symmetryOverlay.DragEndCommand = (ICommand)new Command("PixiEditor.Document.EndDragSymmetry") { UseProvided = true }.ProvideValue(null);
-        symmetryOverlay.DragStartCommand = (ICommand)new Command("PixiEditor.Document.StartDragSymmetry") { UseProvided = true }.ProvideValue(null);
-    }
-
     private void OnImageSizeChanged(object? sender, DocumentSizeChangedEventArgs e)
     {
         PropertyChanged?.Invoke(this, new(nameof(TargetBitmap)));

+ 162 - 0
src/PixiEditor.AvaloniaUI/Views/Main/ViewportControls/ViewportOverlays.cs

@@ -0,0 +1,162 @@
+using System.Collections.ObjectModel;
+using System.Windows.Input;
+using Avalonia;
+using Avalonia.Data;
+using Avalonia.Input;
+using PixiEditor.AvaloniaUI.Helpers.Converters;
+using PixiEditor.AvaloniaUI.Models.Commands.XAML;
+using PixiEditor.AvaloniaUI.Views.Overlays;
+using PixiEditor.AvaloniaUI.Views.Overlays.LineToolOverlay;
+using PixiEditor.AvaloniaUI.Views.Overlays.SelectionOverlay;
+using PixiEditor.AvaloniaUI.Views.Overlays.SymmetryOverlay;
+using PixiEditor.AvaloniaUI.Views.Visuals;
+
+namespace PixiEditor.AvaloniaUI.Views.Main.ViewportControls;
+
+internal class ViewportOverlays
+{
+    public Viewport Viewport { get; set; }
+
+    private GridLines gridLinesOverlay;
+    private SelectionOverlay selectionOverlay;
+    private SymmetryOverlay symmetryOverlay;
+    private LineToolOverlay lineToolOverlay;
+
+    public void Init(Viewport viewport)
+    {
+        Viewport = viewport;
+        gridLinesOverlay = new GridLines();
+        BindGridLines();
+
+        selectionOverlay = new SelectionOverlay();
+        BindSelectionOverlay();
+
+        symmetryOverlay = new SymmetryOverlay();
+        BindSymmetryOverlay();
+
+        lineToolOverlay = new LineToolOverlay();
+        BindLineToolOverlay();
+
+        Viewport.ActiveOverlays.Add(gridLinesOverlay);
+        Viewport.ActiveOverlays.Add(selectionOverlay);
+        Viewport.ActiveOverlays.Add(symmetryOverlay);
+        Viewport.ActiveOverlays.Add(lineToolOverlay);
+    }
+
+    private void BindGridLines()
+    {
+        Binding isVisBinding = new()
+        {
+            Source = Viewport,
+            Path = "GridLinesVisible",
+            Mode = BindingMode.OneWay
+        };
+
+        Binding binding = new()
+        {
+            Source = Viewport,
+            Path = "Document.Width",
+            Mode = BindingMode.OneWay
+        };
+
+        gridLinesOverlay.Bind(GridLines.PixelWidthProperty, binding);
+        gridLinesOverlay.Bind(GridLines.ColumnsProperty, binding);
+
+        binding = new Binding
+        {
+            Source = Viewport,
+            Path = "Document.Height",
+            Mode = BindingMode.OneWay
+        };
+
+        gridLinesOverlay.Bind(GridLines.PixelHeightProperty, binding);
+        gridLinesOverlay.Bind(GridLines.RowsProperty, binding);
+        gridLinesOverlay.Bind(Visual.IsVisibleProperty, isVisBinding);
+    }
+
+    private void BindSelectionOverlay()
+    {
+        Binding showFillBinding = new()
+        {
+            Source = Viewport,
+            Path = "Document.ToolsSubViewModel.ActiveTool",
+            Converter = new IsSelectionToolConverter(),
+            Mode = BindingMode.OneWay
+        };
+
+        Binding pathBinding = new()
+        {
+            Source = Viewport,
+            Path = "Document.SelectionPathBindable",
+            Mode = BindingMode.OneWay
+        };
+
+        Binding isVisibleBinding = new()
+        {
+            Source = Viewport,
+            Path = "Document.SelectionPathBindable",
+            Mode = BindingMode.OneWay,
+            Converter = new NotNullToVisibilityConverter()
+        };
+
+        selectionOverlay.Bind(SelectionOverlay.ShowFillProperty, showFillBinding);
+        selectionOverlay.Bind(SelectionOverlay.PathProperty, pathBinding);
+        selectionOverlay.Bind(Visual.IsVisibleProperty, isVisibleBinding);
+    }
+
+    private void BindSymmetryOverlay()
+    {
+        Binding sizeBinding = new() { Source = Viewport, Path = "Document.SizeBindable", Mode = BindingMode.OneWay };
+        Binding isHitTestVisibleBinding = new() {Source = Viewport, Path = "ZoomMode", Converter = new ZoomModeToHitTestVisibleConverter(), Mode = BindingMode.OneWay };
+        Binding horizontalAxisVisibleBinding = new() {Source = Viewport, Path = "Document.HorizontalSymmetryAxisEnabledBindable", Mode = BindingMode.OneWay };
+        Binding verticalAxisVisibleBinding = new() {Source = Viewport, Path = "Document.VerticalSymmetryAxisEnabledBindable", Mode = BindingMode.OneWay };
+        Binding horizontalAxisYBinding = new() {Source = Viewport, Path = "Document.HorizontalSymmetryAxisYBindable", Mode = BindingMode.OneWay };
+        Binding verticalAxisXBinding = new() {Source = Viewport, Path = "Document.VerticalSymmetryAxisXBindable", Mode = BindingMode.OneWay };
+
+        symmetryOverlay.Bind(SymmetryOverlay.SizeProperty, sizeBinding);
+        symmetryOverlay.Bind(InputElement.IsHitTestVisibleProperty, isHitTestVisibleBinding);
+        symmetryOverlay.Bind(SymmetryOverlay.HorizontalAxisVisibleProperty, horizontalAxisVisibleBinding);
+        symmetryOverlay.Bind(SymmetryOverlay.VerticalAxisVisibleProperty, verticalAxisVisibleBinding);
+        symmetryOverlay.Bind(SymmetryOverlay.HorizontalAxisYProperty, horizontalAxisYBinding);
+        symmetryOverlay.Bind(SymmetryOverlay.VerticalAxisXProperty, verticalAxisXBinding);
+        symmetryOverlay.DragCommand = (ICommand)new Command("PixiEditor.Document.DragSymmetry") { UseProvided = true }.ProvideValue(null);
+        symmetryOverlay.DragEndCommand = (ICommand)new Command("PixiEditor.Document.EndDragSymmetry") { UseProvided = true }.ProvideValue(null);
+        symmetryOverlay.DragStartCommand = (ICommand)new Command("PixiEditor.Document.StartDragSymmetry") { UseProvided = true }.ProvideValue(null);
+    }
+
+    private void BindLineToolOverlay()
+    {
+        Binding isVisibleBinding = new()
+        {
+            Source = Viewport,
+            Path = "Document.LineToolOverlayViewModel.IsEnabled",
+            Mode = BindingMode.OneWay
+        };
+
+        Binding actionCompletedBinding = new()
+        {
+            Source = Viewport,
+            Path = "Document.LineToolOverlayViewModel.ActionCompletedCommand",
+            Mode = BindingMode.OneWay
+        };
+
+        Binding lineStartBinding = new()
+        {
+            Source = Viewport,
+            Path = "Document.LineToolOverlayViewModel.LineStart",
+            Mode = BindingMode.TwoWay
+        };
+
+        Binding lineEndBinding = new()
+        {
+            Source = Viewport,
+            Path = "Document.LineToolOverlayViewModel.LineEnd",
+            Mode = BindingMode.TwoWay
+        };
+
+        lineToolOverlay.Bind(Visual.IsVisibleProperty, isVisibleBinding);
+        lineToolOverlay.Bind(LineToolOverlay.ActionCompletedProperty, actionCompletedBinding);
+        lineToolOverlay.Bind(LineToolOverlay.LineStartProperty, lineStartBinding);
+        lineToolOverlay.Bind(LineToolOverlay.LineEndProperty, lineEndBinding);
+    }
+}

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

@@ -1,11 +1,12 @@
 using Avalonia.Controls;
 using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.Extensions.UI.Overlays;
 
 namespace PixiEditor.AvaloniaUI.Views.Overlays.Handles;
 
 public class AnchorHandle : RectangleHandle
 {
-    public AnchorHandle(Control owner) : base(owner)
+    public AnchorHandle(Overlay owner) : base(owner)
     {
         Size = new VecD(GetResource<double>("AnchorHandleSize"));
     }

+ 20 - 22
src/PixiEditor.AvaloniaUI/Views/Overlays/Handles/Handle.cs

@@ -15,8 +15,8 @@ public abstract class Handle : IHandle
 {
     public IBrush HandleBrush { get; set; } = GetBrush("HandleBackgroundBrush");
     public IPen? HandlePen { get; set; }
-    public double ZoomboxScale { get; set; } = 1.0;
-    public Control Owner { get; set; } = null!;
+    public double ZoomScale { get; set; } = 1.0;
+    public IOverlay Owner { get; set; } = null!;
     public VecD Position { get; set; }
     public VecD Size { get; set; }
     public RectD HandleRect => new(Position, Size);
@@ -31,20 +31,20 @@ public abstract class Handle : IHandle
     private bool isPressed;
     private bool isHovered;
 
-    public Handle(Control owner)
+    public Handle(IOverlay owner)
     {
         Owner = owner;
         Position = VecD.Zero;
         Size = Application.Current.TryGetResource("HandleSize", out object size) ? new VecD((double)size) : new VecD(16);
 
-        Owner.PointerPressed += OnPointerPressed;
-        Owner.PointerMoved += OnPointerMoved;
-        Owner.PointerReleased += OnPointerReleased;
+        Owner.PointerPressedOverlay += OnPointerPressed;
+        Owner.PointerMovedOverlay += OnPointerMoved;
+        Owner.PointerReleasedOverlay += OnPointerReleased;
     }
 
     public abstract void Draw(DrawingContext context);
 
-    public virtual void OnPressed(PointerPressedEventArgs args) { }
+    public virtual void OnPressed(OverlayPointerArgs args) { }
 
     public virtual bool IsWithinHandle(VecD handlePos, VecD pos, double zoomboxScale)
     {
@@ -93,32 +93,30 @@ public abstract class Handle : IHandle
         return Brushes.Black;
     }
 
-    private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
+    private void OnPointerPressed(OverlayPointerArgs args)
     {
-        if (e.GetMouseButton(Owner) != MouseButton.Left)
+        if (args.PointerButton != MouseButton.Left)
         {
             return;
         }
 
-        VecD pos = TransformHelper.ToVecD(e.GetPosition(Owner));
         VecD handlePos = Position;
 
-        if (IsWithinHandle(handlePos, pos, ZoomboxScale))
+        if (IsWithinHandle(handlePos, args.Point, ZoomScale))
         {
-            e.Handled = true;
-            OnPressed(e);
-            OnPress?.Invoke(this, pos);
+            args.Handled = true;
+            OnPressed(args);
+            OnPress?.Invoke(this, args.Point);
             isPressed = true;
-            e.Pointer.Capture(Owner);
+            args.Pointer.Capture(Owner);
         }
     }
 
-    protected virtual void OnPointerMoved(object? sender, PointerEventArgs e)
+    protected virtual void OnPointerMoved(OverlayPointerArgs args)
     {
-        VecD pos = TransformHelper.ToVecD(e.GetPosition(Owner));
         VecD handlePos = Position;
 
-        bool isWithinHandle = IsWithinHandle(handlePos, pos, ZoomboxScale);
+        bool isWithinHandle = IsWithinHandle(handlePos, args.Point, ZoomScale);
 
         if (!isHovered && isWithinHandle)
         {
@@ -142,12 +140,12 @@ public abstract class Handle : IHandle
             return;
         }
 
-        OnDrag?.Invoke(this, pos);
+        OnDrag?.Invoke(this, args.Point);
     }
 
-    private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
+    private void OnPointerReleased(OverlayPointerArgs args)
     {
-        if (e.InitialPressMouseButton != MouseButton.Left)
+        if (args.InitialPressMouseButton != MouseButton.Left)
         {
             return;
         }
@@ -156,7 +154,7 @@ public abstract class Handle : IHandle
         {
             isPressed = false;
             OnRelease?.Invoke(this);
-            e.Pointer.Capture(null);
+            args.Pointer.Capture(null);
         }
     }
 }

+ 3 - 2
src/PixiEditor.AvaloniaUI/Views/Overlays/Handles/OriginAnchor.cs

@@ -1,6 +1,7 @@
 using Avalonia.Controls;
 using Avalonia.Media;
 using PixiEditor.AvaloniaUI.Views.Overlays.TransformOverlay;
+using PixiEditor.Extensions.UI.Overlays;
 
 namespace PixiEditor.AvaloniaUI.Views.Overlays.Handles;
 
@@ -8,14 +9,14 @@ public class OriginAnchor : Handle
 {
     public IPen? SecondaryHandlePen { get; set; } = new Pen(Brushes.White, 1);
 
-    public OriginAnchor(Control owner) : base(owner)
+    public OriginAnchor(Overlay owner) : base(owner)
     {
 
     }
 
     public override void Draw(DrawingContext context)
     {
-        double radius = Size.LongestAxis / ZoomboxScale / 2;
+        double radius = Size.LongestAxis / ZoomScale / 2;
         context.DrawEllipse(HandleBrush, HandlePen, TransformHelper.ToPoint(Position), radius, radius);
         context.DrawEllipse(HandleBrush, SecondaryHandlePen, TransformHelper.ToPoint(Position), radius, radius);
     }

+ 4 - 3
src/PixiEditor.AvaloniaUI/Views/Overlays/Handles/RectangleHandle.cs

@@ -1,20 +1,21 @@
 using Avalonia.Controls;
 using Avalonia.Media;
 using PixiEditor.AvaloniaUI.Views.Overlays.TransformOverlay;
+using PixiEditor.Extensions.UI.Overlays;
 
 namespace PixiEditor.AvaloniaUI.Views.Overlays.Handles;
 
 public class RectangleHandle : Handle
 {
     public double AnchorRadius { get; set; } = GetResource<double>("AnchorRadius");
-    public RectangleHandle(Control owner) : base(owner)
+    public RectangleHandle(Overlay owner) : base(owner)
     {
     }
 
     public override void Draw(DrawingContext context)
     {
-        double scaleMultiplier = (1.0 / ZoomboxScale);
+        double scaleMultiplier = (1.0 / ZoomScale);
         double radius = AnchorRadius * scaleMultiplier;
-        context.DrawRectangle(HandleBrush, HandlePen, TransformHelper.ToHandleRect(Position, Size, ZoomboxScale), radius, radius);
+        context.DrawRectangle(HandleBrush, HandlePen, TransformHelper.ToHandleRect(Position, Size, ZoomScale), radius, radius);
     }
 }

+ 5 - 4
src/PixiEditor.AvaloniaUI/Views/Overlays/Handles/TransformHandle.cs

@@ -3,6 +3,7 @@ using Avalonia.Input;
 using Avalonia.Media;
 using PixiEditor.AvaloniaUI.Views.Overlays.TransformOverlay;
 using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.Extensions.UI.Overlays;
 
 namespace PixiEditor.AvaloniaUI.Views.Overlays.Handles;
 
@@ -13,7 +14,7 @@ public class TransformHandle : Handle
 
     private HandleGlyph handleGeometry;
 
-    public TransformHandle(Control owner) : base(owner)
+    public TransformHandle(Overlay owner) : base(owner)
     {
         handleGeometry = GetHandleGlyph("MoveHandle");
         handleGeometry.Size = Size - new VecD(1, 1);
@@ -23,10 +24,10 @@ public class TransformHandle : Handle
 
     public override void Draw(DrawingContext context)
     {
-        double scaleMultiplier = (1.0 / ZoomboxScale);
+        double scaleMultiplier = (1.0 / ZoomScale);
         double radius = AnchorRadius * scaleMultiplier;
 
-        context.DrawRectangle(HandleBrush, HandlePen, TransformHelper.ToHandleRect(Position, Size, ZoomboxScale), radius, radius);
-        handleGeometry.Draw(context, ZoomboxScale, Position);
+        context.DrawRectangle(HandleBrush, HandlePen, TransformHelper.ToHandleRect(Position, Size, ZoomScale), radius, radius);
+        handleGeometry.Draw(context, ZoomScale, Position);
     }
 }

+ 17 - 30
src/PixiEditor.AvaloniaUI/Views/Overlays/LineToolOverlay/LineToolOverlay.cs

@@ -9,6 +9,7 @@ using PixiEditor.AvaloniaUI.Models.Controllers.InputDevice;
 using PixiEditor.AvaloniaUI.Views.Overlays.Handles;
 using PixiEditor.AvaloniaUI.Views.Overlays.TransformOverlay;
 using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.Extensions.UI.Overlays;
 
 namespace PixiEditor.AvaloniaUI.Views.Overlays.LineToolOverlay;
 internal class LineToolOverlay : Overlay
@@ -42,7 +43,8 @@ internal class LineToolOverlay : Overlay
 
     static LineToolOverlay()
     {
-        AffectsRender<LineToolOverlay>(ZoomScaleProperty, LineStartProperty, LineEndProperty);
+        LineStartProperty.Changed.Subscribe(RenderAffectingPropertyChanged);
+        LineEndProperty.Changed.Subscribe(RenderAffectingPropertyChanged);
     }
 
     private Pen blackPen = new Pen(Brushes.Black, 1);
@@ -53,8 +55,6 @@ internal class LineToolOverlay : Overlay
 
     private bool movedWhileMouseDown = false;
 
-    private MouseUpdateController mouseUpdateController;
-
     private RectangleHandle startHandle;
     private RectangleHandle endHandle;
     private TransformHandle moveHandle;
@@ -77,20 +77,6 @@ internal class LineToolOverlay : Overlay
         moveHandle.HandlePen = blackPen;
         moveHandle.OnDrag += MoveHandleOnDrag;
         AddHandle(moveHandle);
-
-        Loaded += OnLoaded;
-        Unloaded += OnUnloaded;
-    }
-
-    private void OnLoaded(object? sender, RoutedEventArgs e)
-    {
-        //TODO: Ensure this bug doesn't happen in Avalonia, currently Handle classes are taking care of dragging events
-        //mouseUpdateController = new MouseUpdateController(this, MouseMoved);
-    }
-    
-    private void OnUnloaded(object? sender, RoutedEventArgs e)
-    {
-        //mouseUpdateController?.Dispose();
     }
 
     protected override void ZoomChanged(double newZoom)
@@ -111,23 +97,17 @@ internal class LineToolOverlay : Overlay
         moveHandle.Draw(context);
     }
 
-    protected override void OnPointerPressed(PointerPressedEventArgs e)
+    protected override void OnOverlayPointerPressed(OverlayPointerArgs args)
     {
-        base.OnPointerPressed(e);
-
-        MouseButton changedButton = e.GetMouseButton(this);
-
-        if (changedButton != MouseButton.Left)
+        if (args.PointerButton != MouseButton.Left)
             return;
 
-        VecD pos = TransformHelper.ToVecD(e.GetPosition(this));
-
         movedWhileMouseDown = false;
-        mouseDownPos = pos;
+        mouseDownPos = args.Point;
         lineStartOnMouseDown = LineStart;
         lineEndOnMouseDown = LineEnd;
 
-        e.Pointer.Capture(this);
+        args.Pointer.Capture(this);
     }
 
     private void StartHandleOnDrag(Handle source, VecD position)
@@ -152,13 +132,20 @@ internal class LineToolOverlay : Overlay
         movedWhileMouseDown = true;
     }
 
-    protected override void OnPointerReleased(PointerReleasedEventArgs e)
+    protected override void OnOverlayPointerReleased(OverlayPointerArgs args)
     {
-        base.OnPointerReleased(e);
-        if (e.InitialPressMouseButton != MouseButton.Left)
+        if (args.InitialPressMouseButton != MouseButton.Left)
             return;
 
         if (movedWhileMouseDown && ActionCompleted is not null && ActionCompleted.CanExecute(null))
             ActionCompleted.Execute(null);
     }
+
+    private static void RenderAffectingPropertyChanged(AvaloniaPropertyChangedEventArgs<VecD> e)
+    {
+        if (e.Sender is LineToolOverlay overlay)
+        {
+            overlay.Refresh();
+        }
+    }
 }

+ 66 - 28
src/PixiEditor.AvaloniaUI/Views/Overlays/Overlay.cs

@@ -6,10 +6,11 @@ using Avalonia.Data;
 using Avalonia.Input;
 using PixiEditor.AvaloniaUI.Views.Overlays.Handles;
 using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.Extensions.UI.Overlays;
 
 namespace PixiEditor.AvaloniaUI.Views.Overlays;
 
-public abstract class Overlay : Decorator // TODO: Maybe make it not avalonia element
+public abstract class Overlay : Decorator, IOverlay // TODO: Maybe make it not avalonia element
 {
     public List<Handle> Handles { get; } = new();
 
@@ -24,9 +25,58 @@ public abstract class Overlay : Decorator // TODO: Maybe make it not avalonia el
 
     public event Action? RefreshRequested;
 
+    public event PointerEvent? PointerEnteredOverlay;
+    public event PointerEvent? PointerExitedOverlay;
+    public event PointerEvent? PointerMovedOverlay;
+    public event PointerEvent? PointerPressedOverlay;
+    public event PointerEvent? PointerReleasedOverlay;
+
     public Overlay()
     {
-        ZoomScaleProperty.Changed.Subscribe(OnZoomboxScaleChanged);
+        ZoomScaleProperty.Changed.Subscribe(OnZoomScaleChanged);
+    }
+
+    protected virtual void ZoomChanged(double newZoom) { }
+
+    public void Refresh()
+    {
+        RefreshRequested?.Invoke(); // For scene hosted overlays
+        InvalidateVisual(); // For elements in visual tree
+    }
+
+    public void EnterPointer(OverlayPointerArgs args)
+    {
+        OnOverlayPointerEntered(args);
+        PointerEnteredOverlay?.Invoke(args);
+    }
+
+    public void ExitPointer(OverlayPointerArgs args)
+    {
+        OnOverlayPointerExited(args);
+        PointerExitedOverlay?.Invoke(args);
+    }
+
+    public void MovePointer(OverlayPointerArgs args)
+    {
+        OnOverlayPointerMoved(args);
+        PointerMovedOverlay?.Invoke(args);
+    }
+
+    public void PressPointer(OverlayPointerArgs args)
+    {
+        OnOverlayPointerPressed(args);
+        PointerPressedOverlay?.Invoke(args);
+    }
+
+    public void ReleasePointer(OverlayPointerArgs args)
+    {
+        OnOverlayPointerReleased(args);
+        PointerReleasedOverlay?.Invoke(args);
+    }
+
+    public virtual bool TestHit(VecD point)
+    {
+        return Handles.Any(handle => handle.IsWithinHandle(handle.Position, new VecD(point.X, point.Y), ZoomScale));
     }
 
     public void AddHandle(Handle handle)
@@ -55,52 +105,40 @@ public abstract class Overlay : Decorator // TODO: Maybe make it not avalonia el
         }
     }
 
-    protected virtual void ZoomChanged(double newZoom) { }
-
-    private static void OnZoomboxScaleChanged(AvaloniaPropertyChangedEventArgs<double> e)
+    protected virtual void OnOverlayPointerReleased(OverlayPointerArgs args)
     {
-        if (e.Sender is Overlay overlay)
-        {
-            overlay.ZoomChanged(e.NewValue.Value);
-            foreach (var handle in overlay.Handles)
-            {
-                handle.ZoomboxScale = e.NewValue.Value;
-            }
-        }
-    }
 
-    public virtual bool TestHit(VecD point)
-    {
-        return Handles.Any(handle => handle.HandleRect.ContainsInclusive(new VecD(point.X, point.Y)));
     }
 
-    public void Refresh()
+    protected virtual void OnOverlayPointerPressed(OverlayPointerArgs args)
     {
-        RefreshRequested?.Invoke(); // For scene hosted overlays
-        InvalidateVisual(); // For elements in visual tree
-    }
 
-    public virtual void PointerEnteredOverlay(OverlayPointerArgs args)
-    {
     }
 
-    public virtual void PointerExitedOverlay(OverlayPointerArgs args)
+    protected virtual void OnOverlayPointerMoved(OverlayPointerArgs args)
     {
 
     }
 
-    public virtual void PointerMovedOverlay(OverlayPointerArgs args)
+    protected virtual void OnOverlayPointerExited(OverlayPointerArgs args)
     {
 
     }
 
-    public virtual void PointerPressedOverlay(OverlayPointerArgs args)
+    protected virtual void OnOverlayPointerEntered(OverlayPointerArgs args)
     {
 
     }
 
-    public virtual void PointerReleasedOverlay(OverlayPointerArgs args)
+    private static void OnZoomScaleChanged(AvaloniaPropertyChangedEventArgs<double> e)
     {
-
+        if (e.Sender is Overlay overlay)
+        {
+            overlay.ZoomChanged(e.NewValue.Value);
+            foreach (var handle in overlay.Handles)
+            {
+                handle.ZoomScale = e.NewValue.Value;
+            }
+        }
     }
 }

+ 0 - 6
src/PixiEditor.AvaloniaUI/Views/Overlays/Pointers/IOverlayPointer.cs

@@ -1,6 +0,0 @@
-namespace PixiEditor.AvaloniaUI.Views.Overlays.Pointers;
-
-public interface IOverlayPointer
-{
-    public void Capture(Overlay? overlay);
-}

+ 8 - 2
src/PixiEditor.AvaloniaUI/Views/Overlays/Pointers/MouseOverlayPointer.cs

@@ -1,5 +1,6 @@
 using Avalonia.Input;
 using PixiEditor.AvaloniaUI.Views.Visuals;
+using PixiEditor.Extensions.UI.Overlays;
 
 namespace PixiEditor.AvaloniaUI.Views.Overlays.Pointers;
 
@@ -14,8 +15,13 @@ internal class MouseOverlayPointer : IOverlayPointer
         this.captureAction = captureAction;
     }
 
-    public void Capture(Overlay? overlay)
+    public void Capture(IOverlay? overlay)
     {
-        captureAction(overlay, pointer);
+        if (overlay is not Overlay visualOverlay)
+        {
+            return;
+        }
+
+        captureAction(visualOverlay, pointer);
     }
 }

+ 6 - 5
src/PixiEditor.AvaloniaUI/Views/Overlays/SymmetryOverlay/SymmetryOverlay.cs

@@ -10,6 +10,7 @@ using PixiEditor.AvaloniaUI.Views.Overlays.Handles;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Extensions.Common.Localization;
+using PixiEditor.Extensions.UI.Overlays;
 
 namespace PixiEditor.AvaloniaUI.Views.Overlays.SymmetryOverlay;
 #nullable enable
@@ -288,7 +289,7 @@ internal class SymmetryOverlay : Overlay
         Refresh();
     }
 
-    public override void PointerPressedOverlay(OverlayPointerArgs args)
+    protected override void OnOverlayPointerPressed(OverlayPointerArgs args)
     {
         if (args.PointerButton != MouseButton.Left)
             return;
@@ -301,14 +302,14 @@ internal class SymmetryOverlay : Overlay
         CallSymmetryDragStartCommand(dir.Value);
     }
 
-    public override void PointerEnteredOverlay(OverlayPointerArgs args)
+    protected override void OnOverlayPointerEntered(OverlayPointerArgs args)
     {
         pointerPosition = args.Point;
         var dir = IsTouchingHandle(pointerPosition);
         UpdateHovered(dir);
     }
 
-    public override void PointerMovedOverlay(OverlayPointerArgs args)
+    protected override void OnOverlayPointerMoved(OverlayPointerArgs args)
     {
         UpdateHovered(IsTouchingHandle(args.Point));
 
@@ -341,12 +342,12 @@ internal class SymmetryOverlay : Overlay
         }
     }
 
-    public override void PointerExitedOverlay(OverlayPointerArgs args)
+    protected override void OnOverlayPointerExited(OverlayPointerArgs args)
     {
         UpdateHovered(null);
     }
 
-    public override void PointerReleasedOverlay(OverlayPointerArgs e)
+    protected override void OnOverlayPointerReleased(OverlayPointerArgs e)
     {
         if (e.InitialPressMouseButton != MouseButton.Left)
             return;

+ 10 - 2
src/PixiEditor.AvaloniaUI/Views/Visuals/GridLines.cs

@@ -51,7 +51,7 @@ public class GridLines : Overlay
 
     static GridLines()
     {
-        AffectsRender<GridLines>(ColumnsProperty, RowsProperty);
+        IsVisibleProperty.Changed.Subscribe(OnIsVisibleChanged);
     }
 
     public GridLines()
@@ -61,7 +61,7 @@ public class GridLines : Overlay
 
     protected override void ZoomChanged(double newZoom)
     {
-        IsVisible = visibilityConverter.Check(newZoom);
+        IsVisible = IsVisible && visibilityConverter.Check(newZoom);
     }
 
     public override void Render(DrawingContext context)
@@ -92,4 +92,12 @@ public class GridLines : Overlay
             context.DrawLine(pen2, new Point(0, y), new Point(width, y));
         }
     }
+
+    private static void OnIsVisibleChanged(AvaloniaPropertyChangedEventArgs<bool> e)
+    {
+        if (e.Sender is GridLines gridLines)
+        {
+            gridLines.Refresh();
+        }
+    }
 }

+ 29 - 10
src/PixiEditor.AvaloniaUI/Views/Visuals/Scene.cs

@@ -19,6 +19,7 @@ using PixiEditor.AvaloniaUI.Views.Overlays.TransformOverlay;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface;
 using PixiEditor.DrawingApi.Skia;
+using PixiEditor.Extensions.UI.Overlays;
 using Image = PixiEditor.DrawingApi.Core.Surface.ImageData.Image;
 using Point = Avalonia.Point;
 
@@ -192,18 +193,21 @@ internal class Scene : Control, ICustomHitTest
         base.OnPointerEntered(e);
         if (ActiveOverlays != null)
         {
+            OverlayPointerArgs args = ConstructPointerArgs(e);
             if (captured)
             {
-                capturedOverlay?.PointerEnteredOverlay(ConstructPointerArgs(e));
+                capturedOverlay?.EnterPointer(args);
             }
             else
             {
                 foreach (Overlay overlay in ActiveOverlays)
                 {
                     if (!overlay.IsVisible) continue;
-                    overlay.PointerEnteredOverlay(ConstructPointerArgs(e));
+                    overlay.EnterPointer(args);
                 }
             }
+
+            e.Handled = args.Handled;
         }
     }
 
@@ -212,18 +216,22 @@ internal class Scene : Control, ICustomHitTest
         base.OnPointerMoved(e);
         if (ActiveOverlays != null)
         {
+            OverlayPointerArgs args = ConstructPointerArgs(e);
+
             if (captured)
             {
-                capturedOverlay?.PointerMovedOverlay(ConstructPointerArgs(e));
+                capturedOverlay?.MovePointer(args);
             }
             else
             {
                 foreach (Overlay overlay in ActiveOverlays)
                 {
                     if (!overlay.IsVisible) continue;
-                    overlay.PointerMovedOverlay(ConstructPointerArgs(e));
+                    overlay.MovePointer(args);
                 }
             }
+
+            e.Handled = args.Handled;
         }
     }
 
@@ -232,18 +240,22 @@ internal class Scene : Control, ICustomHitTest
         base.OnPointerPressed(e);
         if (ActiveOverlays != null)
         {
+            OverlayPointerArgs args = ConstructPointerArgs(e);
             if (captured)
             {
-                capturedOverlay?.PointerPressedOverlay(ConstructPointerArgs(e));
+                capturedOverlay?.PressPointer(args);
             }
             else
             {
                 foreach (Overlay overlay in ActiveOverlays)
                 {
+                    if(args.Handled) break;
                     if (!overlay.IsVisible) continue;
-                    overlay.PointerPressedOverlay(ConstructPointerArgs(e));
+                    overlay.PressPointer(args);
                 }
             }
+
+            e.Handled = args.Handled;
         }
     }
 
@@ -252,11 +264,15 @@ internal class Scene : Control, ICustomHitTest
         base.OnPointerExited(e);
         if (ActiveOverlays != null)
         {
+            OverlayPointerArgs args = ConstructPointerArgs(e);
             foreach (Overlay overlay in ActiveOverlays)
             {
+                if(args.Handled) break;
                 if (!overlay.IsVisible) continue;
-                overlay.PointerExitedOverlay(ConstructPointerArgs(e));
+                overlay.ExitPointer(args);
             }
+
+            e.Handled = args.Handled;
         }
     }
 
@@ -265,16 +281,19 @@ internal class Scene : Control, ICustomHitTest
         base.OnPointerExited(e);
         if (ActiveOverlays != null)
         {
+            OverlayPointerArgs args = ConstructPointerArgs(e);
+
             if (captured)
             {
-                capturedOverlay?.PointerReleasedOverlay(ConstructPointerArgs(e));
+                capturedOverlay?.ReleasePointer(args);
             }
             else
             {
                 foreach (Overlay overlay in ActiveOverlays)
                 {
+                    if(args.Handled) break;
                     if (!overlay.IsVisible) continue;
-                    overlay.PointerReleasedOverlay(ConstructPointerArgs(e));
+                    overlay.ReleasePointer(args);
                 }
             }
         }
@@ -288,7 +307,7 @@ internal class Scene : Control, ICustomHitTest
             Modifiers = e.KeyModifiers,
             Pointer = new MouseOverlayPointer(e.Pointer, CaptureOverlay),
             PointerButton = e.GetMouseButton(this),
-            InitialPressMouseButton = e is PointerReleasedEventArgs released ? released.InitialPressMouseButton : MouseButton.None
+            InitialPressMouseButton = e is PointerReleasedEventArgs released ? released.InitialPressMouseButton : MouseButton.None,
         };
     }
 

+ 4 - 6
src/PixiEditor.Extensions/UI/Overlays/IHandle.cs

@@ -1,16 +1,14 @@
-using Avalonia.Controls;
-using Avalonia.Input;
-using Avalonia.Media;
+using Avalonia.Media;
 
 namespace PixiEditor.Extensions.UI.Overlays;
 
 public interface IHandle
 {
-    public Control Owner { get; }
+    public IOverlay Owner { get; }
     public IBrush HandleBrush { get; set; }
     public IPen? HandlePen { get; set; }
-    public double ZoomboxScale { get; set; }
+    public double ZoomScale { get; set; }
 
     public void Draw(DrawingContext context);
-    protected void OnPressed(PointerPressedEventArgs args);
+    protected void OnPressed(OverlayPointerArgs args);
 }

+ 24 - 0
src/PixiEditor.Extensions/UI/Overlays/IOverlay.cs

@@ -0,0 +1,24 @@
+using Avalonia.Input;
+using PixiEditor.DrawingApi.Core.Numerics;
+
+namespace PixiEditor.Extensions.UI.Overlays;
+
+public delegate void PointerEvent(OverlayPointerArgs args);
+public interface IOverlay
+{
+    public void EnterPointer(OverlayPointerArgs args);
+    public void ExitPointer(OverlayPointerArgs args);
+    public void MovePointer(OverlayPointerArgs args);
+    public void PressPointer(OverlayPointerArgs args);
+    public void ReleasePointer(OverlayPointerArgs args);
+
+    public void Refresh();
+    public bool TestHit(VecD point);
+
+    public event PointerEvent PointerEnteredOverlay;
+    public event PointerEvent PointerExitedOverlay;
+    public event PointerEvent PointerMovedOverlay;
+    public event PointerEvent PointerPressedOverlay;
+    public event PointerEvent PointerReleasedOverlay;
+    public Cursor Cursor { get; set; }
+}

+ 6 - 0
src/PixiEditor.Extensions/UI/Overlays/IOverlayPointer.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.Extensions.UI.Overlays;
+
+public interface IOverlayPointer
+{
+    public void Capture(IOverlay? overlay);
+}

+ 3 - 4
src/PixiEditor.AvaloniaUI/Views/Overlays/OverlayPointerArgs.cs → src/PixiEditor.Extensions/UI/Overlays/OverlayPointerArgs.cs

@@ -1,15 +1,14 @@
 using Avalonia.Input;
-using PixiEditor.AvaloniaUI.Views.Overlays.Pointers;
 using PixiEditor.DrawingApi.Core.Numerics;
 
-namespace PixiEditor.AvaloniaUI.Views.Overlays;
+namespace PixiEditor.Extensions.UI.Overlays;
 
-public struct OverlayPointerArgs
+public class OverlayPointerArgs
 {
     public VecD Point { get; set; }
     public KeyModifiers Modifiers { get; set; }
     public MouseButton PointerButton { get; set; }
     public MouseButton InitialPressMouseButton { get; set; }
-
     public IOverlayPointer Pointer { get; set; }
+    public bool Handled { get; set; }
 }