浏览代码

Overlay changes wip

Krzysztof Krysiński 1 年之前
父节点
当前提交
4989943306

+ 7 - 2
src/PixiEditor.AvaloniaUI/Helpers/Converters/ThresholdVisibilityConverter.cs

@@ -10,9 +10,14 @@ internal class ThresholdVisibilityConverter
     public bool CheckIfLess { get; set; } = false;
 
     public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        return Check((double)value);
+    }
+
+    public bool Check(double value)
     {
         return CheckIfLess
-            ? (double)value < Threshold
-            : (double)value >= Threshold;
+            ? value < Threshold
+            : value >= Threshold;
     }
 }

+ 9 - 0
src/PixiEditor.AvaloniaUI/Helpers/MathUtil.cs

@@ -0,0 +1,9 @@
+namespace PixiEditor.AvaloniaUI.Helpers;
+
+public class MathUtil
+{
+    public static double AngleToRadians(double angle)
+    {
+        return angle * Math.PI / 180;
+    }
+}

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

@@ -126,6 +126,7 @@
             Focusable="False" Name="scene"
             RenderTransformOrigin="0,0"
             ZIndex="1"
+            IsHitTestVisible="False"
             Width="{Binding RealDimensions.X, ElementName=vpUc}"
             Height="{Binding RealDimensions.Y, ElementName=vpUc}"
             Surface="{Binding TargetBitmap, ElementName=vpUc}"
@@ -135,6 +136,7 @@
             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}">
@@ -195,7 +197,7 @@
                                         FadeOut="{Binding Source={viewModels:ToolVM ColorPickerToolViewModel}, Path=!PickFromReferenceLayer, Mode=OneWay}"
                                         RenderTransformOrigin="0, 0" RenderTransform="{Binding #zoombox.CanvasTransform}"/>
 
-        <Grid IsHitTestVisible="False" RenderTransformOrigin="0, 0" RenderTransform="{Binding #zoombox.CanvasTransform}"
+        <!--<Grid IsHitTestVisible="False" RenderTransformOrigin="0, 0" RenderTransform="{Binding #zoombox.CanvasTransform}"
               ShowGridLines="True"
               Panel.ZIndex="10"
               IsVisible="{Binding GridLinesVisible, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}}">
@@ -203,11 +205,11 @@
                 <converters:ThresholdVisibilityConverter Threshold="10"
                                                          x:Key="ThresholdVisibilityConverter" />
             </Grid.Resources>
-            <!--<visuals:GridLines Scale="{Binding #zoombox.Scale}"
+            <visuals:GridLines Scale="{Binding #zoombox.Scale}"
                                PixelWidth="{Binding Document.Width}" PixelHeight="{Binding Document.Height}"
                                IsVisible="{Binding #zoombox.Scale, Converter={StaticResource ThresholdVisibilityConverter}}"
-                               Rows="{Binding Document.Width}" Columns="{Binding Document.Height}" />-->
-        </Grid>
+                               Rows="{Binding Document.Width}" Columns="{Binding Document.Height}" />
+        </Grid>-->
 
         <!--<Grid ZIndex="5" DataContext="{Binding ElementName=vpUc}"
               RenderTransformOrigin="0, 0" RenderTransform="{Binding #zoombox.CanvasTransform}">

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

@@ -1,4 +1,5 @@
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Windows.Input;
 using Avalonia;
@@ -15,6 +16,7 @@ using PixiEditor.AvaloniaUI.Models.Controllers.InputDevice;
 using PixiEditor.AvaloniaUI.Models.DocumentModels;
 using PixiEditor.AvaloniaUI.Models.Position;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
+using PixiEditor.AvaloniaUI.Views.Overlays;
 using PixiEditor.AvaloniaUI.Views.Visuals;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface;
@@ -281,21 +283,26 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
 
     public PixiEditor.Zoombox.Zoombox Zoombox => zoombox;
 
+    public ObservableCollection<Overlay> ActiveOverlays { get; } = new();
+
     public Guid GuidValue { get; } = Guid.NewGuid();
 
     private MouseUpdateController? mouseUpdateController;
+    private GridLines _gridLinesOverlay;
 
     static Viewport()
     {
         DocumentProperty.Changed.Subscribe(OnDocumentChange);
         ZoomViewportTriggerProperty.Changed.Subscribe(ZoomViewportTriggerChanged);
         CenterViewportTriggerProperty.Changed.Subscribe(CenterViewportTriggerChanged);
+        GridLinesVisibleProperty.Changed.Subscribe(GridLinesVisibleChanged);
     }
 
     public Viewport()
     {
         InitializeComponent();
 
+        _gridLinesOverlay = new GridLines();
         MainImage!.Loaded += OnImageLoaded;
         MainImage.SizeChanged += OnMainImageSizeChanged;
         Loaded += OnLoad;
@@ -355,6 +362,43 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         newDoc?.Operations.AddOrUpdateViewport(viewport.GetLocation());
     }
 
+    private static void GridLinesVisibleChanged(AvaloniaPropertyChangedEventArgs<bool> e)
+    {
+        Viewport? viewport = (Viewport)e.Sender;
+        if (e.NewValue.Value)
+        {
+            BindGridLines(viewport);
+            viewport.ActiveOverlays.Add(viewport._gridLinesOverlay);
+        }
+        else
+        {
+            viewport.ActiveOverlays.Remove(viewport._gridLinesOverlay);
+        }
+    }
+
+    private static void BindGridLines(Viewport viewport)
+    {
+        Binding binding = new()
+        {
+            Source = viewport.Document,
+            Path = "Width",
+            Mode = BindingMode.OneWay
+        };
+
+        viewport._gridLinesOverlay.Bind(GridLines.PixelWidthProperty, binding);
+        viewport._gridLinesOverlay.Bind(GridLines.ColumnsProperty, binding);
+
+        binding = new Binding
+        {
+            Source = viewport.Document,
+            Path = "Height",
+            Mode = BindingMode.OneWay
+        };
+
+        viewport._gridLinesOverlay.Bind(GridLines.PixelHeightProperty, binding);
+        viewport._gridLinesOverlay.Bind(GridLines.RowsProperty, binding);
+    }
+
     private void OnImageSizeChanged(object? sender, DocumentSizeChangedEventArgs e)
     {
         PropertyChanged?.Invoke(this, new(nameof(TargetBitmap)));

+ 1 - 1
src/PixiEditor.AvaloniaUI/Views/Overlays/LineToolOverlay/LineToolOverlay.cs

@@ -110,7 +110,7 @@ internal class LineToolOverlay : Overlay
         endHandle.Position = LineEnd;
         VecD center = (LineStart + LineEnd) / 2;
         VecD size = LineEnd - LineStart;
-        moveHandle.Position = TransformHelper.GetHandlePos(new ShapeCorners(center, size), ZoomboxScale, moveHandle.Size);
+        moveHandle.Position = TransformHelper.GetHandlePos(new ShapeCorners(center, size), ZoomScale, moveHandle.Size);
 
         startHandle.Draw(context);
         endHandle.Draw(context);

+ 6 - 2
src/PixiEditor.AvaloniaUI/Views/Overlays/Overlay.cs

@@ -1,6 +1,7 @@
 using System.Collections.Generic;
 using Avalonia;
 using Avalonia.Controls;
+using Avalonia.Data;
 using PixiEditor.AvaloniaUI.Views.Overlays.Handles;
 
 namespace PixiEditor.AvaloniaUI.Views.Overlays;
@@ -10,9 +11,9 @@ public class Overlay : Decorator
     public List<Handle> Handles { get; } = new();
 
     public static readonly StyledProperty<double> ZoomboxScaleProperty =
-        AvaloniaProperty.Register<Overlay, double>(nameof(ZoomboxScale), defaultValue: 1.0);
+        AvaloniaProperty.Register<Overlay, double>(nameof(ZoomScale), defaultValue: 1.0);
 
-    public double ZoomboxScale
+    public double ZoomScale
     {
         get => GetValue(ZoomboxScaleProperty);
         set => SetValue(ZoomboxScaleProperty, value);
@@ -49,10 +50,13 @@ public class Overlay : Decorator
         }
     }
 
+    protected virtual void ZoomChanged(double newZoom) { }
+
     private static void OnZoomboxScaleChanged(AvaloniaPropertyChangedEventArgs<double> e)
     {
         if (e.Sender is Overlay overlay)
         {
+            overlay.ZoomChanged(e.NewValue.Value);
             foreach (var handle in overlay.Handles)
             {
                 handle.ZoomboxScale = e.NewValue.Value;

+ 7 - 7
src/PixiEditor.AvaloniaUI/Views/Overlays/TransformOverlay/TransformOverlay.cs

@@ -216,7 +216,7 @@ internal class TransformOverlay : Overlay
     public override void Render(DrawingContext drawingContext)
     {
         base.Render(drawingContext);
-        DrawOverlay(drawingContext, new(Bounds.Width, Bounds.Height), Corners, InternalState.Origin, ZoomboxScale);
+        DrawOverlay(drawingContext, new(Bounds.Width, Bounds.Height), Corners, InternalState.Origin, ZoomScale);
 
         if (capturedAnchor is null)
             UpdateRotationCursor(TransformHelper.ToVecD(lastPointerPos));
@@ -286,7 +286,7 @@ internal class TransformOverlay : Overlay
         leftHandle.Position = left;
         rightHandle.Position = right;
         originHandle.Position = InternalState.Origin;
-        moveHandle.Position = TransformHelper.GetHandlePos(Corners, ZoomboxScale, moveHandle.Size);
+        moveHandle.Position = TransformHelper.GetHandlePos(Corners, ZoomScale, moveHandle.Size);
 
         topLeftHandle.Draw(context);
         topRightHandle.Draw(context);
@@ -341,7 +341,7 @@ internal class TransformOverlay : Overlay
 
         VecD pos = TransformHelper.ToVecD(e.GetPosition(this));
 
-        if(Handles.Any(x => x.IsWithinHandle(x.Position, pos, ZoomboxScale))) return;
+        if(Handles.Any(x => x.IsWithinHandle(x.Position, pos, ZoomScale))) return;
 
         if (!CanRotate(pos))
         {
@@ -388,7 +388,7 @@ internal class TransformOverlay : Overlay
             finalCursor = new Cursor(StandardCursorType.None);
         }
 
-        Anchor? anchor = TransformHelper.GetAnchorInPosition(pos, Corners, InternalState.Origin, ZoomboxScale, topLeftHandle.Size);
+        Anchor? anchor = TransformHelper.GetAnchorInPosition(pos, Corners, InternalState.Origin, ZoomScale, topLeftHandle.Size);
 
         if (isRotating)
         {
@@ -485,7 +485,7 @@ internal class TransformOverlay : Overlay
 
     private bool CanRotate(VecD mousePos)
     {
-        return !Corners.IsPointInside(mousePos) && Handles.All(x => !x.IsWithinHandle(x.Position, mousePos, ZoomboxScale));
+        return !Corners.IsPointInside(mousePos) && Handles.All(x => !x.IsWithinHandle(x.Position, mousePos, ZoomScale));
     }
 
     private bool UpdateRotationCursor(VecD mousePos)
@@ -499,7 +499,7 @@ internal class TransformOverlay : Overlay
         var matrix = new TranslateTransform(mousePos.X, mousePos.Y).Value;
         double angle = (mousePos - InternalState.Origin).Angle * 180 / Math.PI - 90;
         matrix = matrix.RotateAt(angle, mousePos.X, mousePos.Y);
-        matrix = matrix.ScaleAt(8 / ZoomboxScale, 8 / ZoomboxScale, mousePos.X, mousePos.Y);
+        matrix = matrix.ScaleAt(8 / ZoomScale, 8 / ZoomScale, mousePos.X, mousePos.Y);
         rotateCursorGeometry.Transform = new MatrixTransform(matrix);
         return true;
     }
@@ -569,7 +569,7 @@ internal class TransformOverlay : Overlay
             if (snapPoint == originHandle)
                 continue;
 
-            if (TransformHelper.IsWithinHandle(snapPoint.Position, pos, ZoomboxScale, topHandle.Size))
+            if (TransformHelper.IsWithinHandle(snapPoint.Position, pos, ZoomScale, topHandle.Size))
             {
                 snapped = true;
                 return snapPoint.Position;

+ 16 - 14
src/PixiEditor.AvaloniaUI/Views/Visuals/GridLines.cs

@@ -2,15 +2,12 @@
 using Avalonia.Controls;
 using Avalonia.Media;
 using PixiEditor.AvaloniaUI.Helpers.Converters;
+using PixiEditor.AvaloniaUI.Views.Overlays;
 
 namespace PixiEditor.AvaloniaUI.Views.Visuals;
 
-public class GridLines : Control
+public class GridLines : Overlay
 {
-    public static readonly StyledProperty<double> ScaleProperty = AvaloniaProperty.Register<GridLines, double>(
-        nameof(Scale),
-        defaultValue: 1d);
-
     public static readonly StyledProperty<int> RowsProperty = AvaloniaProperty.Register<GridLines, int>(
         nameof(Rows));
 
@@ -47,19 +44,24 @@ public class GridLines : Control
         set => SetValue(RowsProperty, value);
     }
 
-    public double Scale
-    {
-        get => GetValue(ScaleProperty);
-        set => SetValue(ScaleProperty, value);
-    }
-
     private const double PenWidth = 0.8d;
     private Pen pen1 = new(Brushes.Black, PenWidth);
     private Pen pen2 = new(Brushes.White, PenWidth);
+    private ThresholdVisibilityConverter visibilityConverter = new(){ Threshold = 10 };
 
     static GridLines()
     {
-        AffectsRender<GridLines>(ColumnsProperty, RowsProperty, ScaleProperty);
+        AffectsRender<GridLines>(ColumnsProperty, RowsProperty);
+    }
+
+    public GridLines()
+    {
+        IsHitTestVisible = false;
+    }
+
+    protected override void ZoomChanged(double newZoom)
+    {
+        IsVisible = visibilityConverter.Check(newZoom);
     }
 
     public override void Render(DrawingContext context)
@@ -73,8 +75,8 @@ public class GridLines : Control
         double columnWidth = width / Columns;
         double rowHeight = height / Rows;
 
-        pen1.Thickness = ReciprocalConverter.Convert(Scale);
-        pen2.Thickness = ReciprocalConverter.Convert(Scale, 1.2);
+        pen1.Thickness = ReciprocalConverter.Convert(ZoomScale);
+        pen2.Thickness = ReciprocalConverter.Convert(ZoomScale, 1.2);
 
         for (int i = 0; i < Columns; i++)
         {

+ 59 - 25
src/PixiEditor.AvaloniaUI/Views/Visuals/Scene.cs

@@ -1,4 +1,5 @@
 using System.Collections.ObjectModel;
+using System.Collections.Specialized;
 using Avalonia;
 using Avalonia.Animation;
 using Avalonia.Controls;
@@ -7,6 +8,7 @@ using Avalonia.Rendering.SceneGraph;
 using Avalonia.Skia;
 using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
+using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Helpers.Converters;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
 using PixiEditor.AvaloniaUI.Views.Overlays;
@@ -122,6 +124,7 @@ internal class Scene : Control
         FlipYProperty.Changed.AddClassHandler<Scene>(RequestRendering);
         FadeOutProperty.Changed.AddClassHandler<Scene>(FadeOutChanged);
         CheckerImagePathProperty.Changed.AddClassHandler<Scene>(CheckerImagePathChanged);
+        ActiveOverlaysProperty.Changed.AddClassHandler<Scene>(ActiveOverlaysChanged);
     }
 
     public Scene()
@@ -137,20 +140,55 @@ internal class Scene : Control
     {
         if (Surface == null || Document == null) return;
 
-        var operation = new DrawSceneOperation(Surface, Document, ContentPosition, Scale, Angle, FlipX, FlipY, Bounds,
-            Opacity, (SKBitmap)checkerBitmap.Native);
+        float finalScale = CalculateFinalScale();
+        context.PushTransform(Matrix.CreateTranslation(ContentPosition.X, ContentPosition.Y));
+        context.PushTransform(Matrix.CreateScale(finalScale, finalScale));
+
+        float angle = (float)Angle;
+        if (FlipX)
+        {
+            angle = 360 - angle;
+        }
 
+        if (FlipY)
+        {
+            angle = 360 - angle;
+        }
+
+        context.PushTransform(Matrix.CreateRotation(MathUtil.AngleToRadians(angle)));
+        context.PushTransform(Matrix.CreateScale(FlipX ? -1 : 1, FlipY ? -1 : 1));
+
+        var operation = new DrawSceneOperation(Surface, Document, ContentPosition, finalScale, Angle, FlipX, FlipY, Bounds,
+            Opacity, (SKBitmap)checkerBitmap.Native);
         context.Custom(operation);
 
         if (ActiveOverlays != null)
         {
             foreach (Overlay overlay in ActiveOverlays)
             {
+                overlay.ZoomScale = finalScale;
+                if(!overlay.IsVisible) continue;
+
                 overlay.Render(context);
             }
         }
     }
 
+    private float CalculateFinalScale()
+    {
+        var scaleUniform = CalculateResolutionScale();
+        float scale = (float)Scale * scaleUniform;
+        return scale;
+    }
+
+    private float CalculateResolutionScale()
+    {
+        float scaleX = (float)Document.Width / Surface.Size.X;
+        float scaleY = (float)Document.Height / Surface.Size.Y;
+        var scaleUniform = Math.Min(scaleX, scaleY);
+        return scaleUniform;
+    }
+
     private static void BoundsChanged(Scene sender, AvaloniaPropertyChangedEventArgs e)
     {
         sender.InvalidateVisual();
@@ -166,6 +204,24 @@ internal class Scene : Control
         scene.Opacity = e.NewValue is true ? 0 : 1;
     }
 
+    private static void ActiveOverlaysChanged(Scene scene, AvaloniaPropertyChangedEventArgs e)
+    {
+        scene.InvalidateVisual();
+        if (e.OldValue is ObservableCollection<Overlay> oldOverlays)
+        {
+            oldOverlays.CollectionChanged -= scene.OverlayCollectionChanged;
+        }
+        if (e.NewValue is ObservableCollection<Overlay> newOverlays)
+        {
+            newOverlays.CollectionChanged += scene.OverlayCollectionChanged;
+        }
+    }
+
+    private void OverlayCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
+    {
+        InvalidateVisual();
+    }
+
     private static void CheckerImagePathChanged(Scene scene, AvaloniaPropertyChangedEventArgs e)
     {
         if (e.NewValue is string path)
@@ -226,9 +282,7 @@ internal class DrawSceneOperation : SkiaDrawOperation
 
         canvas.Save();
 
-        float finalScale = CalculateFinalScale();
-
-        RectI surfaceRectToRender = FindRectToRender(finalScale);
+        RectI surfaceRectToRender = FindRectToRender((float)Scale);
 
         if (surfaceRectToRender.IsZeroOrNegativeArea)
         {
@@ -237,7 +291,6 @@ internal class DrawSceneOperation : SkiaDrawOperation
             return;
         }
 
-        canvas.Scale(finalScale, finalScale, ContentPosition.X, ContentPosition.Y);
         float angle = (float)Angle;
         if (FlipX)
         {
@@ -249,10 +302,6 @@ internal class DrawSceneOperation : SkiaDrawOperation
             angle = 360 - angle;
         }
 
-        canvas.RotateDegrees(angle, ContentPosition.X, ContentPosition.Y);
-        canvas.Scale(FlipX ? -1 : 1, FlipY ? -1 : 1, ContentPosition.X, ContentPosition.Y);
-        canvas.Translate(ContentPosition.X, ContentPosition.Y);
-
         DrawCheckerboard(canvas, surfaceRectToRender);
 
         using Image snapshot = Surface.DrawingSurface.Snapshot(surfaceRectToRender);
@@ -324,21 +373,6 @@ internal class DrawSceneOperation : SkiaDrawOperation
         };
     }
 
-    private float CalculateFinalScale()
-    {
-        var scaleUniform = CalculateResolutionScale();
-        float scale = (float)Scale * scaleUniform;
-        return scale;
-    }
-
-    private float CalculateResolutionScale()
-    {
-        float scaleX = (float)Document.Width / Surface.Size.X;
-        float scaleY = (float)Document.Height / Surface.Size.Y;
-        var scaleUniform = Math.Min(scaleX, scaleY);
-        return scaleUniform;
-    }
-
     private VecD SurfaceToViewport(VecI surfacePoint, float scale)
     {
         VecD unscaledPoint = surfacePoint * scale;