Sfoglia il codice sorgente

Merge pull request #644 from PixiEditor/fixes

Some fixes & improvements
Krzysztof Krysiński 9 mesi fa
parent
commit
eac2ca0627
25 ha cambiato i file con 160 aggiunte e 49 eliminazioni
  1. 1 1
      src/Drawie
  2. BIN
      src/PixiEditor.UI.Common/Fonts/PixiPerfect.ttf
  3. 5 2
      src/PixiEditor.UI.Common/Fonts/PixiPerfectIcons.axaml
  4. 6 3
      src/PixiEditor.UI.Common/Fonts/PixiPerfectIcons.axaml.cs
  5. 1 0
      src/PixiEditor.UI.Common/PixiEditor.UI.Common.csproj
  6. 3 0
      src/PixiEditor/Helpers/ThemeResources.cs
  7. 2 2
      src/PixiEditor/Models/Handlers/IToolHandler.cs
  8. 1 2
      src/PixiEditor/ViewModels/SubViewModels/IoViewModel.cs
  9. 5 2
      src/PixiEditor/ViewModels/SubViewModels/ToolsViewModel.cs
  10. 5 2
      src/PixiEditor/ViewModels/Tools/ShapeTool.cs
  11. 2 2
      src/PixiEditor/ViewModels/Tools/ToolViewModel.cs
  12. 15 6
      src/PixiEditor/ViewModels/Tools/Tools/MoveToolViewModel.cs
  13. 1 1
      src/PixiEditor/ViewModels/Tools/Tools/MoveViewportToolViewModel.cs
  14. 1 1
      src/PixiEditor/ViewModels/Tools/Tools/PenToolViewModel.cs
  15. 4 2
      src/PixiEditor/ViewModels/Tools/Tools/RasterEllipseToolViewModel.cs
  16. 4 2
      src/PixiEditor/ViewModels/Tools/Tools/RasterLineToolViewModel.cs
  17. 4 2
      src/PixiEditor/ViewModels/Tools/Tools/RasterRectangleToolViewModel.cs
  18. 1 1
      src/PixiEditor/ViewModels/Tools/Tools/RotateViewportToolViewModel.cs
  19. 5 3
      src/PixiEditor/ViewModels/Tools/Tools/VectorEllipseToolViewModel.cs
  20. 5 3
      src/PixiEditor/ViewModels/Tools/Tools/VectorLineToolViewModel.cs
  21. 5 3
      src/PixiEditor/ViewModels/Tools/Tools/VectorRectangleToolViewModel.cs
  22. 1 1
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml
  23. 16 3
      src/PixiEditor/Views/Overlays/Drawables/InfoBox.cs
  24. 23 0
      src/PixiEditor/Views/Overlays/TransformOverlay/TransformHelper.cs
  25. 44 5
      src/PixiEditor/Views/Overlays/TransformOverlay/TransformOverlay.cs

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit 63d856f0bf2240882a7a27cf256fc64ee2323a99
+Subproject commit f1b0d435bb5a916def8a7eed99fd71bf2749cc30

BIN
src/PixiEditor.UI.Common/Fonts/pixiperfect.ttf → src/PixiEditor.UI.Common/Fonts/PixiPerfect.ttf


+ 5 - 2
src/PixiEditor.UI.Common/Fonts/PixiPerfectIcons.axaml

@@ -3,7 +3,7 @@
                     xmlns:system="clr-namespace:System;assembly=System.Runtime">
     <Styles.Resources>
         <ResourceDictionary>
-            <FontFamily x:Key="PixiPerfectIcons">avares://PixiEditor.UI.Common/Fonts/pixiperfect.ttf#pixiperfect</FontFamily>
+            <FontFamily x:Key="PixiPerfectIcons">avares://PixiEditor.UI.Common/Fonts/PixiPerfect.ttf#PixiPerfect</FontFamily>
             
             <system:String x:Key="icon-add-reference">&#xE900;</system:String>
             <system:String x:Key="icon-add-to-mask">&#xE901;</system:String>
@@ -132,7 +132,10 @@
             <system:String x:Key="icon-nodes">&#xe984;</system:String>
             <system:String x:Key="icon-onion">&#xe985;</system:String>
             
-            <system:String x:Key="icon-snapping">&#xfffd;</system:String>
+            <system:String x:Key="icon-lowres-circle">&#xe986;</system:String>
+            <system:String x:Key="icon-snapping">&#xe987;</system:String>
+            <system:String x:Key="icon-lowres-square">&#xe988;</system:String>
+            <system:String x:Key="icon-lowres-line">&#xe989;</system:String>
         </ResourceDictionary>
     </Styles.Resources>
     

+ 6 - 3
src/PixiEditor.UI.Common/Fonts/PixiPerfectIcons.axaml.cs

@@ -10,7 +10,7 @@ namespace PixiEditor.UI.Common.Fonts;
 //TODO: Make autogenerated from PixiPerfectIcons.axaml
 public static class PixiPerfectIcons
 {
-    private static readonly FontFamily pixiPerfectFontFamily = new("avares://PixiEditor.UI.Common/Fonts/pixiperfect.ttf#pixiperfect");
+    private static readonly FontFamily pixiPerfectFontFamily = new("avares://PixiEditor.UI.Common/Fonts/PixiPerfect.ttf#PixiPerfect");
     
     public const string AddReference = "\ue900";
     public const string AddToMask = "\ue901";
@@ -131,10 +131,13 @@ public static class PixiPerfectIcons
     public const string ToggleLayerVisible = "\u25a1;"; // TODO: Create a toggle layer visible icon
     public const string ToggleMask = "\u25a1;"; // TODO: Create a toggle mask icon
     public static string Pen => "\uE971";
-    
+    public static string LowResCircle => "\uE986";
+    public static string LowResSquare => "\uE988";
+    public static string LowResLine => "\uE989";
+
     public static Stream GetFontStream()
     {
-        return AssetLoader.Open(new Uri("avares://PixiEditor.UI.Common/Fonts/pixiperfect.ttf"));
+        return AssetLoader.Open(new Uri("avares://PixiEditor.UI.Common/Fonts/PixiPerfect.ttf"));
     }
 
     public static IImage ToIcon(string unicode, double size = 18)

+ 1 - 0
src/PixiEditor.UI.Common/PixiEditor.UI.Common.csproj

@@ -11,6 +11,7 @@
       <AvaloniaResource Include="Fonts\pixiperfect.ttf" />
       <None Remove="Assets\Animations\LoadingIndicator.json" />
       <AvaloniaResource Include="Assets\Animations\LoadingIndicator.json" />
+      <AvaloniaResource Include="Fonts\PixiPerfect.ttf" />
     </ItemGroup>
 
     <ItemGroup>

+ 3 - 0
src/PixiEditor/Helpers/ThemeResources.cs

@@ -16,4 +16,7 @@ public static class ThemeResources
     
     public static Color BackgroundColor =>
         ResourceLoader.GetResource<SolidColorBrush>("ThemeBackgroundBrush", Application.Current.ActualThemeVariant).Color.ToColor();
+
+    public static Color BorderMidColor =>
+        ResourceLoader.GetResource<SolidColorBrush>("ThemeBorderMidBrush", Application.Current.ActualThemeVariant).Color.ToColor();
 }

+ 2 - 2
src/PixiEditor/Models/Handlers/IToolHandler.cs

@@ -58,7 +58,7 @@ internal interface IToolHandler : IHandler
 
     public void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown);
     public void UseTool(VecD pos);
-    public void OnSelected();
+    public void OnSelected(bool restoring);
 
-    public void OnDeselecting();
+    public void OnDeselecting(bool transient);
 }

+ 1 - 2
src/PixiEditor/ViewModels/SubViewModels/IoViewModel.cs

@@ -280,8 +280,7 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
             return;
         var tools = Owner.ToolsSubViewModel;
 
-        var rightCanUp = (button == MouseButton.Right && tools.RightClickMode == RightClickMode.Erase ||
-                          tools.RightClickMode == RightClickMode.SecondaryColor);
+        var rightCanUp = (button == MouseButton.Right) && tools.RightClickMode is RightClickMode.Erase or RightClickMode.SecondaryColor;
 
         if (button == MouseButton.Left || rightCanUp)
         {

+ 5 - 2
src/PixiEditor/ViewModels/SubViewModels/ToolsViewModel.cs

@@ -223,10 +223,11 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
 
         if (ActiveTool != null)
         {
-            ActiveTool.OnDeselecting();
+            ActiveTool.OnDeselecting(transient);
             ActiveTool.Toolbar.SettingChanged -= ToolbarSettingChanged;
         }
 
+        bool wasTransient = ActiveTool?.IsTransient ?? false;
         if (ActiveTool != null) ActiveTool.IsTransient = false;
         bool shareToolbar = EnableSharedToolbar;
         if (ActiveTool is not null)
@@ -247,13 +248,15 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
         }
 
         if (LastActionTool != ActiveTool)
+        {
             SelectedToolChanged?.Invoke(this, new SelectedToolEventArgs(LastActionTool, ActiveTool));
+        }
 
         //update old tool
         LastActionTool?.ModifierKeyChanged(false, false, false);
         //update new tool
         ActiveTool.ModifierKeyChanged(ctrlIsDown, shiftIsDown, altIsDown);
-        ActiveTool.OnSelected();
+        ActiveTool.OnSelected(wasTransient);
 
         tool.IsActive = true;
         ActiveTool.IsTransient = transient;

+ 5 - 2
src/PixiEditor/ViewModels/Tools/ShapeTool.cs

@@ -20,8 +20,11 @@ internal abstract class ShapeTool : ToolViewModel, IShapeToolHandler
         Toolbar = new BasicShapeToolbar();
     }
 
-    public override void OnDeselecting()
+    public override void OnDeselecting(bool transient)
     {
-        ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Operations.TryStopToolLinkedExecutor();
+        if (!transient)
+        {
+            ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Operations.TryStopToolLinkedExecutor();
+        }
     }
 }

+ 2 - 2
src/PixiEditor/ViewModels/Tools/ToolViewModel.cs

@@ -143,11 +143,11 @@ internal abstract class ToolViewModel : ObservableObject, IToolHandler
     public virtual void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown) { }
 
     public virtual void UseTool(VecD pos) { }
-    public virtual void OnSelected() { }
+    public virtual void OnSelected(bool restoring) { }
     
     protected virtual void OnSelectedLayersChanged(IStructureMemberHandler[] layers) { }
 
-    public virtual void OnDeselecting()
+    public virtual void OnDeselecting(bool transient)
     {
     }
 

+ 15 - 6
src/PixiEditor/ViewModels/Tools/Tools/MoveToolViewModel.cs

@@ -77,14 +77,23 @@ internal class MoveToolViewModel : ToolViewModel, IMoveToolHandler
         }
     }
 
-    public override void OnSelected()
+    public override void OnSelected(bool restoring)
     {
+        if (TransformingSelectedArea)
+        {
+            return;
+        }
+        
         ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Operations.TransformSelectedArea(true);
     }
 
-    public override void OnDeselecting()
+    public override void OnDeselecting(bool transient)
     {
-        ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Operations.TryStopToolLinkedExecutor();
+        var vm = ViewModelMain.Current;
+        if (!transient)
+        {
+            vm.DocumentManagerSubViewModel.ActiveDocument?.Operations.TryStopToolLinkedExecutor();
+        }
     }
 
     protected override void OnSelectedLayersChanged(IStructureMemberHandler[] layers)
@@ -93,8 +102,8 @@ internal class MoveToolViewModel : ToolViewModel, IMoveToolHandler
         {
             return;
         }
-        
-        OnDeselecting();
-        OnSelected();
+
+        OnDeselecting(false);
+        OnSelected(false);
     }
 }

+ 1 - 1
src/PixiEditor/ViewModels/Tools/Tools/MoveViewportToolViewModel.cs

@@ -25,7 +25,7 @@ internal class MoveViewportToolViewModel : ToolViewModel
         Cursor = new Cursor(StandardCursorType.SizeAll);
     }
 
-    public override void OnSelected()
+    public override void OnSelected(bool restoring)
     {
         ActionDisplay = new LocalizedString("MOVE_VIEWPORT_ACTION_DISPLAY");
     }

+ 1 - 1
src/PixiEditor/ViewModels/Tools/Tools/PenToolViewModel.cs

@@ -77,7 +77,7 @@ namespace PixiEditor.ViewModels.Tools.Tools
             actualToolSize = oldSetting.Value;
         }
 
-        public override void OnDeselecting()
+        public override void OnDeselecting(bool transient)
         {
             if (!PixelPerfectEnabled)
             {

+ 4 - 2
src/PixiEditor/ViewModels/Tools/Tools/RasterEllipseToolViewModel.cs

@@ -25,7 +25,7 @@ internal class RasterEllipseToolViewModel : ShapeTool, IRasterEllipseToolHandler
     public override LocalizedString Tooltip => new LocalizedString("ELLIPSE_TOOL_TOOLTIP", Shortcut);
     public bool DrawCircle { get; private set; }
 
-    public override string Icon => PixiPerfectIcons.Circle;
+    public override string Icon => PixiPerfectIcons.LowResCircle;
 
     public override Type LayerTypeToCreateOnEmptyUse { get; } = typeof(ImageLayerNode);
 
@@ -48,8 +48,10 @@ internal class RasterEllipseToolViewModel : ShapeTool, IRasterEllipseToolHandler
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseRasterEllipseTool();
     }
     
-    public override void OnSelected()
+    public override void OnSelected(bool restoring)
     {
+        if(restoring) return;
+        
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseRasterEllipseTool();
     }
 }

+ 4 - 2
src/PixiEditor/ViewModels/Tools/Tools/RasterLineToolViewModel.cs

@@ -27,7 +27,7 @@ internal class RasterLineToolViewModel : ShapeTool, ILineToolHandler
     public override LocalizedString Tooltip => new LocalizedString("LINE_TOOL_TOOLTIP", Shortcut);
 
     public override Type[]? SupportedLayerTypes { get; } = { typeof(IRasterLayerHandler) };
-    public override string Icon => PixiPerfectIcons.Line;
+    public override string Icon => PixiPerfectIcons.LowResLine;
 
     [Settings.Inherited] public int ToolSize => GetValue<int>();
 
@@ -54,8 +54,10 @@ internal class RasterLineToolViewModel : ShapeTool, ILineToolHandler
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseRasterLineTool();
     }
 
-    public override void OnSelected()
+    public override void OnSelected(bool restoring)
     {
+        if (restoring) return;
+        
         var document = ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument;
         document.Tools.UseRasterLineTool();
     }

+ 4 - 2
src/PixiEditor/ViewModels/Tools/Tools/RasterRectangleToolViewModel.cs

@@ -26,7 +26,7 @@ internal class RasterRectangleToolViewModel : ShapeTool, IRasterRectangleToolHan
     public bool Filled { get; set; } = false;
     public bool DrawSquare { get; private set; } = false;
 
-    public override string Icon => PixiPerfectIcons.Square;
+    public override string Icon => PixiPerfectIcons.LowResSquare;
 
     public override Type LayerTypeToCreateOnEmptyUse { get; } = typeof(ImageLayerNode);
 
@@ -49,8 +49,10 @@ internal class RasterRectangleToolViewModel : ShapeTool, IRasterRectangleToolHan
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseRasterRectangleTool();
     }
 
-    public override void OnSelected()
+    public override void OnSelected(bool restoring)
     {
+        if(restoring) return;
+        
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseRasterRectangleTool();
     }
 }

+ 1 - 1
src/PixiEditor/ViewModels/Tools/Tools/RotateViewportToolViewModel.cs

@@ -23,7 +23,7 @@ internal class RotateViewportToolViewModel : ToolViewModel
     {
     }
 
-    public override void OnSelected()
+    public override void OnSelected(bool restoring)
     {
         ActionDisplay = new LocalizedString("ROTATE_VIEWPORT_ACTION_DISPLAY");
     }

+ 5 - 3
src/PixiEditor/ViewModels/Tools/Tools/VectorEllipseToolViewModel.cs

@@ -39,8 +39,10 @@ internal class VectorEllipseToolViewModel : ShapeTool, IVectorEllipseToolHandler
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseVectorEllipseTool();
     }
 
-    public override void OnSelected()
+    public override void OnSelected(bool restoring)
     {
+        if (restoring) return;
+        
         var document = ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument;
         var layer = document.SelectedStructureMember;
         if (layer is IVectorLayerHandler vectorLayer && 
@@ -56,7 +58,7 @@ internal class VectorEllipseToolViewModel : ShapeTool, IVectorEllipseToolHandler
 
     protected override void OnSelectedLayersChanged(IStructureMemberHandler[] layers)
     {
-        OnDeselecting();
-        OnSelected();
+        OnDeselecting(false);
+        OnSelected(false);
     }
 }

+ 5 - 3
src/PixiEditor/ViewModels/Tools/Tools/VectorLineToolViewModel.cs

@@ -59,8 +59,10 @@ internal class VectorLineToolViewModel : ShapeTool, IVectorLineToolHandler
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseVectorLineTool();
     }
 
-    public override void OnSelected()
+    public override void OnSelected(bool restoring)
     {
+        if (restoring) return;
+        
         var document = ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument;
         var layer = document.SelectedStructureMember;
         if (layer is IVectorLayerHandler vectorLayer)
@@ -79,7 +81,7 @@ internal class VectorLineToolViewModel : ShapeTool, IVectorLineToolHandler
 
     protected override void OnSelectedLayersChanged(IStructureMemberHandler[] layers)
     {
-        OnDeselecting();
-        OnSelected();
+        OnDeselecting(false);
+        OnSelected(false);
     }
 }

+ 5 - 3
src/PixiEditor/ViewModels/Tools/Tools/VectorRectangleToolViewModel.cs

@@ -51,8 +51,10 @@ internal class VectorRectangleToolViewModel : ShapeTool, IVectorRectangleToolHan
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseVectorRectangleTool();
     }
 
-    public override void OnSelected()
+    public override void OnSelected(bool restoring)
     {
+        if (restoring) return;
+        
         var document = ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument;
         var layer = document.SelectedStructureMember;
         if (layer is IVectorLayerHandler vectorLayer && 
@@ -68,7 +70,7 @@ internal class VectorRectangleToolViewModel : ShapeTool, IVectorRectangleToolHan
 
     protected override void OnSelectedLayersChanged(IStructureMemberHandler[] layers)
     {
-        OnDeselecting();
-        OnSelected();
+        OnDeselecting(false);
+        OnSelected(false);
     }
 }

+ 1 - 1
src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml

@@ -91,7 +91,7 @@
                                           ui:Translator.TooltipKey="LOW_RES_PREVIEW"
                                           Classes="OverlayButton pixi-icon"
                                           BorderBrush="{DynamicResource ThemeBorderMidBrush}"
-                                          Content="{DynamicResource icon-circle}"
+                                          Content="{DynamicResource icon-lowres-circle}"
                                           IsChecked="{Binding !HighResPreview, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=TwoWay}"
                                           Cursor="Hand" />
                         </StackPanel>

+ 16 - 3
src/PixiEditor/Views/Overlays/Drawables/InfoBox.cs

@@ -18,6 +18,11 @@ public class InfoBox
     {
         Color = Colors.White, StrokeWidth = 1, Style = PaintStyle.Fill, IsAntiAliased = true
     };
+    
+    private Paint borderPen = new Paint()
+    {
+        Color = Colors.Black, StrokeWidth = 1, Style = PaintStyle.Stroke, IsAntiAliased = true
+    };
 
     public double ZoomScale { get; set; } = 1;
 
@@ -28,21 +33,29 @@ public class InfoBox
         font = ThemeResources.ThemeFont;
         fontPen.Color = ThemeResources.ForegroundColor;
         backgroundPen.Color = ThemeResources.BackgroundColor;
+        borderPen.Color = ThemeResources.BorderMidColor;
     }
 
     public void DrawInfo(Canvas context, string text, VecD pointerPos)
     {
+        const float padding = 10;
         font.FontSize = 14 / ZoomScale;
 
         double widthTextSize = font.MeasureText(text);
 
         VecD aboveCursor = pointerPos + new VecD(0, -20 / ZoomScale);
-        float rectWidth = (float)widthTextSize + (10 / (float)ZoomScale);
-        float rectHeight = 20 / (float)ZoomScale;
+        float rectWidth = (float)widthTextSize + (padding * 2 / (float)ZoomScale);
+        float rectHeight = (float)font.FontSize + padding / (float)ZoomScale;
         float x = (float)aboveCursor.X - rectWidth / 2;
-        float y = (float)aboveCursor.Y - ((float)font.FontSize);
+        float y = (float)aboveCursor.Y - ((float)font.FontSize) - (padding / 4) / (float)ZoomScale;
+        
         context.DrawRoundRect(x, y, rectWidth, rectHeight, 5 / (float)ZoomScale,
             5 / (float)ZoomScale, backgroundPen);
+        
+        borderPen.StrokeWidth = 1 / (float)ZoomScale;
+        
+        context.DrawRoundRect(x, y, rectWidth, rectHeight, 5 / (float)ZoomScale,
+            5 / (float)ZoomScale, borderPen);
 
         context.DrawText(text, aboveCursor, TextAlign.Center, font, fontPen);
     }

+ 23 - 0
src/PixiEditor/Views/Overlays/TransformOverlay/TransformHelper.cs

@@ -301,4 +301,27 @@ internal static class TransformHelper
             _ => throw new ArgumentException($"{capturedAnchor} is not a corner or a side"),
         };
     }
+
+    public static Anchor GetOppositeAnchor(Anchor anchor)
+    {
+        return anchor switch
+        {
+            Anchor.TopLeft => Anchor.BottomRight,
+            Anchor.TopRight => Anchor.BottomLeft,
+            Anchor.BottomLeft => Anchor.TopRight,
+            Anchor.BottomRight => Anchor.TopLeft,
+            Anchor.Top => Anchor.Bottom,
+            Anchor.Bottom => Anchor.Top,
+            Anchor.Left => Anchor.Right,
+            Anchor.Right => Anchor.Left,
+            _ => throw new ArgumentException($"{anchor} is not a corner or a side"),
+        };
+    }
+
+    public static bool RotationIsAlmostCardinal(double radians, double threshold = 0.03)
+    {
+        double normalized = Math.Abs(radians % (2 * Math.PI));
+        double[] cardinals = { 0, Math.PI / 2, Math.PI, 3 * Math.PI / 2, 2 * Math.PI };
+        return cardinals.Any(cardinal => Math.Abs(normalized - cardinal) < threshold);
+    }
 }

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

@@ -230,6 +230,7 @@ internal class TransformOverlay : Overlay
 
     private VecD lastPointerPos;
     private InfoBox infoBox;
+    private VecD lastSize;
     
     public TransformOverlay()
     {
@@ -291,7 +292,7 @@ internal class TransformOverlay : Overlay
 
     public override void RenderOverlay(Canvas drawingContext, RectD canvasBounds)
     {
-        DrawOverlay(drawingContext, new(Bounds.Width, Bounds.Height), Corners, InternalState.Origin, (float)ZoomScale);
+        DrawOverlay(drawingContext, canvasBounds.Size, Corners, InternalState.Origin, (float)ZoomScale);
 
         if (capturedAnchor is null)
             UpdateRotationCursor(lastPointerPos);
@@ -323,6 +324,7 @@ internal class TransformOverlay : Overlay
     private void DrawOverlay
         (Canvas context, VecD size, ShapeCorners corners, VecD origin, float zoomboxScale)
     {
+        lastSize = size;
         // draw transparent background to enable mouse input
         DrawMouseInputArea(context, size);
 
@@ -709,6 +711,11 @@ internal class TransformOverlay : Overlay
             VecD targetPos = originalAnchorPos + pos - mousePosOnStartAnchorDrag;
 
             VecD projected = targetPos.ProjectOntoLine(originalAnchorPos, InternalState.Origin);
+            if (projected.IsNaNOrInfinity())
+            {
+                projected = targetPos;
+            }
+            
             VecD anchorRelativeDelta = projected - originalAnchorPos;
 
             var adjacentAnchors = TransformHelper.GetAdjacentAnchors((Anchor)capturedAnchor);
@@ -733,12 +740,26 @@ internal class TransformOverlay : Overlay
             }
             else
             {
-                snapped = FindProjectedAnchorSnap(projected);
-                if (snapped.Delta == VecI.Zero)
+                // If rotation is almost cardinal, projecting snapping points result in extreme values when perpendicular to the axis
+                if (!TransformHelper.RotationIsAlmostCardinal(cornersOnStartAnchorDrag.RectRotation))
+                {
+                    snapped = FindProjectedAnchorSnap(projected);
+                    if (snapped.Delta == VecI.Zero)
+                    {
+                        snapped = FindAdjacentCornersSnap(adjacentAnchors, anchorRelativeDelta);
+                    }
+                }
+                else
                 {
-                    snapped = FindAdjacentCornersSnap(adjacentAnchors, anchorRelativeDelta);
+                    snapped = TrySnapAnchor(targetPos);
                 }
             }
+            
+            VecD potentialPos = targetPos + snapped.Delta;
+            if(potentialPos.X < 0 || potentialPos.Y < 0 || potentialPos.X > lastSize.X || potentialPos.Y > lastSize.Y)
+            {
+                snapped = new SnapData();
+            }
 
             ShapeCorners? newCorners = TransformUpdateHelper.UpdateShapeFromSide
             ((Anchor)capturedAnchor, SideFreedom, InternalState.ProportionalAngle1,
@@ -778,7 +799,13 @@ internal class TransformOverlay : Overlay
 
         VecD snapOrigin = TransformHelper.GetAnchorPosition(cornersOnStartAnchorDrag, adjacent) + anchorRelativeDelta;
         var snapped = TrySnapAnchorAlongLine(adjacentAnchorPos, snapOrigin);
-
+        double maxDistance = GetSizeToOppositeSide(cornersOnStartAnchorDrag, capturedAnchor.Value) / 8f;
+        
+        if(snapped.Delta.Length > maxDistance)
+        {
+            snapped = new SnapData();
+        }
+        
         if (snapped.Delta == VecI.Zero)
         {
             adjacentAnchorPos = TransformHelper.GetAnchorPosition(cornersOnStartAnchorDrag, adjacentAnchors.Item2) +
@@ -788,10 +815,22 @@ internal class TransformOverlay : Overlay
             snapOrigin = TransformHelper.GetAnchorPosition(cornersOnStartAnchorDrag, adjacent) + anchorRelativeDelta;
 
             snapped = TrySnapAnchorAlongLine(adjacentAnchorPos, snapOrigin);
+            if(snapped.Delta.Length > maxDistance)
+            {
+                snapped = new SnapData();
+            }
         }
 
         return snapped;
     }
+    
+    private double GetSizeToOppositeSide(ShapeCorners corners, Anchor anchor1)
+    {
+        Anchor opposite = TransformHelper.GetOppositeAnchor(anchor1);
+        VecD oppositePos = TransformHelper.GetAnchorPosition(corners, opposite);
+        VecD anchorPos = TransformHelper.GetAnchorPosition(corners, anchor1);
+        return (oppositePos - anchorPos).Length;
+    }
 
     private SnapData FindProjectedAnchorSnap(VecD projected)
     {