Просмотр исходного кода

Proper input handling for overlays

Krzysztof Krysiński 1 год назад
Родитель
Сommit
dd078ecef1

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

@@ -118,6 +118,23 @@
                 </Border>
             </overlays:TogglableFlyout.Child>
         </overlays:TogglableFlyout>
+        <visuals:Scene
+            Focusable="False" Name="scene"
+            ZIndex="1"
+            Width="{Binding RealDimensions.X, ElementName=vpUc}"
+            Height="{Binding RealDimensions.Y, ElementName=vpUc}"
+            Surface="{Binding TargetBitmap, ElementName=vpUc}"
+            Scale="{Binding Scale, ElementName=zoombox, Mode=OneWay}"
+            Document="{Binding Document, ElementName=vpUc, Mode=OneWay}"
+            ContentPosition="{Binding CanvasPos, ElementName=zoombox, Mode=OneWay}"
+            Angle="{Binding RotateTransformAngle, ElementName=zoombox, Mode=OneWay}"
+            FlipX="{Binding FlipX, ElementName=zoombox, Mode=OneWay}"
+            FlipY="{Binding FlipY, ElementName=zoombox, Mode=OneWay}"
+            ActiveOverlays="{Binding ElementName=vpUc, Path=ActiveOverlays}"
+            FadeOut="{Binding Source={viewModels:ToolVM ColorPickerToolViewModel}, Path=PickOnlyFromReferenceLayer, Mode=OneWay}"
+            CheckerImagePath="/Images/CheckerTile.png"
+            ui1:RenderOptionsBindable.BitmapInterpolationMode="{Binding Scale, Converter={converters:ScaleToBitmapScalingModeConverter}, ElementName=zoombox}">
+        </visuals:Scene>
         <zoombox:Zoombox
             Tag="{Binding ElementName=vpUc}"
             x:Name="zoombox"
@@ -132,7 +149,7 @@
             FlipX="{Binding FlipX, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=TwoWay}"
             FlipY="{Binding FlipY, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=TwoWay}">
             <zoombox:Zoombox.AdditionalContent>
-                <Border
+                <!--<Border
                     d:Width="64"
                     d:Height="64"
                     HorizontalAlignment="Center"
@@ -140,7 +157,7 @@
                     DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}}"
                     RenderOptions.BitmapInterpolationMode="None">
                     <Grid>
-                        <Panel Width="{Binding Document.Width}" Height="{Binding Document.Height}" />
+
                         <Rectangle Stroke="{DynamicResource ThemeBackgroundBrush1}" Opacity=".8" ZIndex="2"
                                    IsVisible="{Binding Document.ReferenceLayerViewModel.IsVisibleBindable}">
                             <Rectangle.StrokeThickness>
@@ -165,26 +182,9 @@
                             </Rectangle.Margin>
                         </Rectangle>
                     </Grid>
-                </Border>
+                </Border>-->
             </zoombox:Zoombox.AdditionalContent>
         </zoombox:Zoombox>
-        <visuals:Scene
-            Focusable="False" Name="scene"
-            ZIndex="1"
-            Width="{Binding RealDimensions.X, ElementName=vpUc}"
-            Height="{Binding RealDimensions.Y, ElementName=vpUc}"
-            Surface="{Binding TargetBitmap, ElementName=vpUc}"
-            Scale="{Binding Scale, ElementName=zoombox, Mode=OneWay}"
-            Document="{Binding Document, ElementName=vpUc, Mode=OneWay}"
-            ContentPosition="{Binding CanvasPos, ElementName=zoombox, Mode=OneWay}"
-            Angle="{Binding RotateTransformAngle, ElementName=zoombox, Mode=OneWay}"
-            FlipX="{Binding FlipX, ElementName=zoombox, Mode=OneWay}"
-            FlipY="{Binding FlipY, ElementName=zoombox, Mode=OneWay}"
-            ActiveOverlays="{Binding ElementName=vpUc, Path=ActiveOverlays}"
-            FadeOut="{Binding Source={viewModels:ToolVM ColorPickerToolViewModel}, Path=PickOnlyFromReferenceLayer, Mode=OneWay}"
-            CheckerImagePath="/Images/CheckerTile.png"
-            ui1:RenderOptionsBindable.BitmapInterpolationMode="{Binding Scale, Converter={converters:ScaleToBitmapScalingModeConverter}, ElementName=zoombox}">
-        </visuals:Scene>
         <overlays:ReferenceLayerOverlay SizeChanged="OnReferenceImageSizeChanged"
                                         ReferenceLayer="{Binding Document.ReferenceLayerViewModel}"
                                         ReferenceLayerScale="{Binding ReferenceLayerScale}"

+ 6 - 10
src/PixiEditor.AvaloniaUI/Views/Main/ViewportControls/Viewport.axaml.cs

@@ -309,8 +309,8 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         InitializeComponent();
 
         builtInOverlays.Init(this);
-        MainImage!.Loaded += OnImageLoaded;
-        MainImage.SizeChanged += OnMainImageSizeChanged;
+        Scene!.Loaded += OnImageLoaded;
+        Scene.SizeChanged += OnMainImageSizeChanged;
         Loaded += OnLoad;
         Unloaded += OnUnload;
 
@@ -319,16 +319,12 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         viewportGrid.AddHandler(PointerPressedEvent, Image_MouseDown, RoutingStrategies.Bubble);
     }
 
-
-
-    public Panel? MainImage => zoombox != null ? (Panel?)((Grid?)((Border?)zoombox.AdditionalContent)?.Child)?.Children[0] : null;
     public Scene Scene => scene;
     public Grid BackgroundGrid => viewportGrid;
 
     private void ForceRefreshFinalImage()
     {
         Scene.InvalidateVisual();
-        MainImage?.InvalidateVisual();
     }
 
     private void OnUnload(object? sender, RoutedEventArgs e)
@@ -347,7 +343,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
     private void InitializeOverlays()
     {
         brushShapeOverlay.MouseEventSource = BackgroundGrid;
-        brushShapeOverlay.MouseReference = MainImage;
+        brushShapeOverlay.MouseReference = Scene;
         brushShapeOverlay.Initialize();
     }
 
@@ -413,7 +409,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
             _ => MouseButton.Middle
         };
 
-        Point pos = e.GetPosition(MainImage);
+        Point pos = e.GetPosition(Scene);
         VecD conv = new VecD(pos.X, pos.Y);
         MouseOnCanvasEventArgs? parameter = new MouseOnCanvasEventArgs(mouseButton, conv);
 
@@ -425,7 +421,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
     {
         if (MouseMoveCommand is null)
             return;
-        Point pos = e.GetPosition(MainImage);
+        Point pos = e.GetPosition(Scene);
         VecD conv = new VecD(pos.X, pos.Y);
 
         MouseButton mouseButton = e.GetCurrentPoint(this).Properties.PointerUpdateKind switch
@@ -446,7 +442,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         if (MouseUpCommand is null)
             return;
 
-        Point pos = e.GetPosition(MainImage);
+        Point pos = e.GetPosition(Scene);
         MouseOnCanvasEventArgs parameter = new(e.InitialPressMouseButton, new VecD(pos.X, pos.Y));
         if (MouseUpCommand.CanExecute(parameter))
             MouseUpCommand.Execute(parameter);

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

@@ -1,3 +1,4 @@
+using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Collections.Specialized;
 using Avalonia;
@@ -8,6 +9,7 @@ using Avalonia.Media;
 using Avalonia.Rendering;
 using Avalonia.Rendering.SceneGraph;
 using Avalonia.Skia;
+using Avalonia.Threading;
 using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.AvaloniaUI.Helpers;
@@ -120,9 +122,10 @@ internal class Scene : Control, ICustomHitTest
     }
 
     private Bitmap? checkerBitmap;
-    private bool captured;
     private Overlay? capturedOverlay;
 
+    private List<Overlay> mouseOverOverlays = new();
+
     static Scene()
     {
         AffectsRender<Scene>(BoundsProperty, WidthProperty, HeightProperty, ScaleProperty, AngleProperty, FlipXProperty,
@@ -190,22 +193,15 @@ internal class Scene : Control, ICustomHitTest
 
     protected override void OnPointerEntered(PointerEventArgs e)
     {
-        //TODO: Invoke on overlay that is within bounds
         base.OnPointerEntered(e);
         if (ActiveOverlays != null)
         {
             OverlayPointerArgs args = ConstructPointerArgs(e);
-            if (captured)
-            {
-                capturedOverlay?.EnterPointer(args);
-            }
-            else
+            foreach (Overlay overlay in ActiveOverlays)
             {
-                foreach (Overlay overlay in ActiveOverlays)
-                {
-                    if (!overlay.IsVisible) continue;
-                    overlay.EnterPointer(args);
-                }
+                if (!overlay.IsVisible || mouseOverOverlays.Contains(overlay) || !overlay.TestHit(args.Point)) continue;
+                overlay.EnterPointer(args);
+                mouseOverOverlays.Add(overlay);
             }
 
             e.Handled = args.Handled;
@@ -214,21 +210,38 @@ internal class Scene : Control, ICustomHitTest
 
     protected override void OnPointerMoved(PointerEventArgs e)
     {
-        //TODO: Invoke on overlay that is within bounds
         base.OnPointerMoved(e);
         if (ActiveOverlays != null)
         {
             OverlayPointerArgs args = ConstructPointerArgs(e);
 
-            if (captured)
+            if (capturedOverlay != null)
             {
-                capturedOverlay?.MovePointer(args);
+                capturedOverlay.MovePointer(args);
             }
             else
             {
                 foreach (Overlay overlay in ActiveOverlays)
                 {
                     if (!overlay.IsVisible) continue;
+
+                    if (overlay.TestHit(args.Point))
+                    {
+                        if (!mouseOverOverlays.Contains(overlay))
+                        {
+                            overlay.EnterPointer(args);
+                            mouseOverOverlays.Add(overlay);
+                        }
+                    }
+                    else
+                    {
+                        if (mouseOverOverlays.Contains(overlay))
+                        {
+                            overlay.ExitPointer(args);
+                            mouseOverOverlays.Remove(overlay);
+                        }
+                    }
+
                     overlay.MovePointer(args);
                 }
             }
@@ -243,15 +256,16 @@ internal class Scene : Control, ICustomHitTest
         if (ActiveOverlays != null)
         {
             OverlayPointerArgs args = ConstructPointerArgs(e);
-            if (captured)
+            if (capturedOverlay != null)
             {
                 capturedOverlay?.PressPointer(args);
             }
             else
             {
-                foreach (Overlay overlay in ActiveOverlays)
+                for (var i = 0; i < mouseOverOverlays.Count; i++)
                 {
-                    if(args.Handled) break;
+                    var overlay = mouseOverOverlays[i];
+                    if (args.Handled) break;
                     if (!overlay.IsVisible) continue;
                     overlay.PressPointer(args);
                 }
@@ -263,16 +277,19 @@ internal class Scene : Control, ICustomHitTest
 
     protected override void OnPointerExited(PointerEventArgs e)
     {
-        //TODO: Invoke on overlay that is out of bounds
         base.OnPointerExited(e);
         if (ActiveOverlays != null)
         {
             OverlayPointerArgs args = ConstructPointerArgs(e);
-            foreach (Overlay overlay in ActiveOverlays)
+            for (var i = 0; i < mouseOverOverlays.Count; i++)
             {
-                if(args.Handled) break;
+                var overlay = mouseOverOverlays[i];
+                if (args.Handled) break;
                 if (!overlay.IsVisible) continue;
+
                 overlay.ExitPointer(args);
+                mouseOverOverlays.Remove(overlay);
+                i--;
             }
 
             e.Handled = args.Handled;
@@ -286,16 +303,18 @@ internal class Scene : Control, ICustomHitTest
         {
             OverlayPointerArgs args = ConstructPointerArgs(e);
 
-            if (captured)
+            if (capturedOverlay != null)
             {
-                capturedOverlay?.ReleasePointer(args);
+                capturedOverlay.ReleasePointer(args);
+                capturedOverlay = null;
             }
             else
             {
-                foreach (Overlay overlay in ActiveOverlays)
+                foreach (Overlay overlay in mouseOverOverlays)
                 {
                     if(args.Handled) break;
                     if (!overlay.IsVisible) continue;
+
                     overlay.ReleasePointer(args);
                 }
             }
@@ -353,15 +372,17 @@ internal class Scene : Control, ICustomHitTest
         if (overlay == null)
         {
             pointer.Capture(null);
-            captured = false;
+            mouseOverOverlays.Clear();
+            capturedOverlay = null;
             return;
         }
 
-        if(overlay != null && !ActiveOverlays.Contains(overlay)) return;
+        if(!ActiveOverlays.Contains(overlay)) return;
 
         pointer.Capture(this);
         capturedOverlay = overlay;
-        captured = true;
+        mouseOverOverlays.Clear();
+        mouseOverOverlays.Add(overlay);
     }
 
     private static void BoundsChanged(Scene sender, AvaloniaPropertyChangedEventArgs e)
@@ -400,7 +421,7 @@ internal class Scene : Control, ICustomHitTest
         {
             foreach (Overlay overlay in e.OldItems)
             {
-                overlay.RefreshRequested -= InvalidateVisual;
+                overlay.RefreshRequested -= QueueRender;
             }
         }
 
@@ -408,11 +429,16 @@ internal class Scene : Control, ICustomHitTest
         {
             foreach (Overlay overlay in e.NewItems)
             {
-                overlay.RefreshRequested += InvalidateVisual;
+                overlay.RefreshRequested += QueueRender;
             }
         }
     }
 
+    private void QueueRender()
+    {
+        Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Render);
+    }
+
     private static void CheckerImagePathChanged(Scene scene, AvaloniaPropertyChangedEventArgs e)
     {
         if (e.NewValue is string path)

+ 1 - 1
src/PixiEditor.Zoombox/Zoombox.axaml.cs

@@ -12,7 +12,7 @@ using PixiEditor.Zoombox.Operations;
 
 namespace PixiEditor.Zoombox;
 
-public partial class Zoombox : UserControl, INotifyPropertyChanged
+public partial class Zoombox : UserControl, INotifyPropertyChanged //TODO: Make it content control
 {
     public static readonly StyledProperty<ZoomboxMode> ZoomModeProperty =
         AvaloniaProperty.Register<Zoombox, ZoomboxMode>(nameof(ZoomMode), defaultValue: ZoomboxMode.Normal);