Răsfoiți Sursa

Added brush shape overlay preview

Krzysztof Krysiński 2 săptămâni în urmă
părinte
comite
90a6bd34b7
20 a modificat fișierele cu 230 adăugiri și 62 ștergeri
  1. 3 1
      src/PixiEditor.ChangeableDocument/Changeables/Brushes/BrushEngine.cs
  2. 35 10
      src/PixiEditor.ChangeableDocument/Changes/Drawing/LineBasedPen_UpdateableChange.cs
  3. 3 2
      src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs
  4. 75 11
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/PenToolExecutor.cs
  5. 4 1
      src/PixiEditor/Models/Handlers/Tools/IPenToolHandler.cs
  6. 1 1
      src/PixiEditor/ViewModels/Tools/ShapeTool.cs
  7. 7 1
      src/PixiEditor/ViewModels/Tools/ToolViewModel.cs
  8. 2 1
      src/PixiEditor/ViewModels/Tools/Tools/BrightnessToolViewModel.cs
  9. 1 1
      src/PixiEditor/ViewModels/Tools/Tools/ColorPickerToolViewModel.cs
  10. 2 1
      src/PixiEditor/ViewModels/Tools/Tools/EraserToolViewModel.cs
  11. 3 1
      src/PixiEditor/ViewModels/Tools/Tools/FloodFillToolViewModel.cs
  12. 4 2
      src/PixiEditor/ViewModels/Tools/Tools/LassoToolViewModel.cs
  13. 2 2
      src/PixiEditor/ViewModels/Tools/Tools/MagicWandToolViewModel.cs
  14. 1 1
      src/PixiEditor/ViewModels/Tools/Tools/MoveToolViewModel.cs
  15. 1 1
      src/PixiEditor/ViewModels/Tools/Tools/MoveViewportToolViewModel.cs
  16. 20 3
      src/PixiEditor/ViewModels/Tools/Tools/PenToolViewModel.cs
  17. 1 1
      src/PixiEditor/ViewModels/Tools/Tools/RotateViewportToolViewModel.cs
  18. 2 1
      src/PixiEditor/ViewModels/Tools/Tools/SelectToolViewModel.cs
  19. 1 1
      src/PixiEditor/ViewModels/Tools/Tools/ZoomToolViewModel.cs
  20. 62 19
      src/PixiEditor/Views/Overlays/BrushShapeOverlay/BrushShapeOverlay.cs

+ 3 - 1
src/PixiEditor.ChangeableDocument/Changeables/Brushes/BrushEngine.cs

@@ -79,6 +79,7 @@ internal class BrushEngine
 
         var fill = brushNode.Fill.Value;
         var stroke = brushNode.Stroke.Value;
+        var paintable = fill;
 
         if (fill != null && fill.AnythingVisible)
         {
@@ -87,6 +88,7 @@ internal class BrushEngine
         else
         {
             strokeStyle = PaintStyle.Stroke;
+            paintable = stroke;
         }
 
         if (vectorShape is PathVectorData pathData)
@@ -94,7 +96,7 @@ internal class BrushEngine
             strokeCap = pathData.StrokeLineCap;
         }
 
-        target.EnqueueDrawPath(path, fill, vectorShape.StrokeWidth,
+        target.EnqueueDrawPath(path, paintable, vectorShape.StrokeWidth,
             strokeCap, brushData.BlendMode, strokeStyle, brushData.AntiAliasing);
 
         if (fill is { AnythingVisible: true } && stroke is { AnythingVisible: true })

+ 35 - 10
src/PixiEditor.ChangeableDocument/Changes/Drawing/LineBasedPen_UpdateableChange.cs

@@ -13,6 +13,7 @@ using PixiEditor.ChangeableDocument.Changeables.Brushes;
 using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Brushes;
+using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.ChangeableDocument.Rendering.ContextData;
 
@@ -124,10 +125,7 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
         {
             brushData = new BrushData(brushData.BrushGraph)
             {
-                StrokeWidth = strokeWidth,
-                AntiAliasing = antiAliasing,
-                Hardness = hardness,
-                Spacing = spacing,
+                StrokeWidth = strokeWidth, AntiAliasing = antiAliasing, Hardness = hardness, Spacing = spacing,
             };
         }
     }
@@ -154,7 +152,8 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
             brushData.Spacing = spacing;
             brushData.StrokeWidth = strokeWidth;
 
-            engine.ExecuteBrush(image, brushData, point, frame, target.ProcessingColorSpace, SamplingOptions.Default, pointerInfo);
+            engine.ExecuteBrush(image, brushData, point, frame, target.ProcessingColorSpace, SamplingOptions.Default,
+                pointerInfo);
 
             /*if (brushData.VectorShape == null)
             {
@@ -190,7 +189,15 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
 
         var affChunks = image.FindAffectedArea(opCount);
 
-        return DrawingChangeHelper.CreateAreaChangeInfo(memberGuid, affChunks, drawOnMask);
+        var changeInfo = DrawingChangeHelper.CreateAreaChangeInfo(memberGuid, affChunks, drawOnMask);
+        List<IChangeInfo> changes = new()
+        {
+            changeInfo.AsT1,
+            new ComputedPropertyValue_ChangeInfo(brushOutputGuid, "VectorShape", true,
+                brushOutputNode.VectorShape.Value)
+        };
+
+        return changes;
     }
 
     private void FastforwardEnqueueDrawLines(ChunkyImage targetImage, KeyFrameTime frameTime)
@@ -205,7 +212,8 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
             var rect = new RectI(points[0] - new VecI((int)(strokeWidth / 2f)), new VecI((int)strokeWidth));
             finalPaintable = color;
 
-            engine.ExecuteBrush(targetImage, brushData, points[0], frameTime, targetImage.ProcessingColorSpace, SamplingOptions.Default, pointerInfo);
+            engine.ExecuteBrush(targetImage, brushData, points[0], frameTime, targetImage.ProcessingColorSpace,
+                SamplingOptions.Default, pointerInfo);
 
             return;
         }
@@ -223,7 +231,8 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
             var rect = new RectI(points[i] - new VecI((int)(strokeWidth / 2f)), new VecI((int)strokeWidth));
             finalPaintable = color;
 
-            engine.ExecuteBrush(targetImage, brushData, points[i], frameTime, targetImage.ProcessingColorSpace, SamplingOptions.Default, pointerInfo);
+            engine.ExecuteBrush(targetImage, brushData, points[i], frameTime, targetImage.ProcessingColorSpace,
+                SamplingOptions.Default, pointerInfo);
         }
     }
 
@@ -258,7 +267,14 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
             storedChunks = new CommittedChunkStorage(image, affArea.Chunks);
             image.CommitChanges();
 
-            return DrawingChangeHelper.CreateAreaChangeInfo(memberGuid, affArea, drawOnMask);
+            var change = DrawingChangeHelper.CreateAreaChangeInfo(memberGuid, affArea, drawOnMask).AsT1;
+            List<IChangeInfo> changes = new()
+            {
+                change,
+                new ComputedPropertyValue_ChangeInfo(brushOutputGuid, "VectorShape", true,
+                    brushOutputNode?.VectorShape.Value)
+            };
+            return changes;
         }
         else
         {
@@ -271,7 +287,16 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
             storedChunks = new CommittedChunkStorage(image, affArea.Chunks);
             image.CommitChanges();
 
-            return DrawingChangeHelper.CreateAreaChangeInfo(memberGuid, affArea, drawOnMask);
+            IChangeInfo info = DrawingChangeHelper.CreateAreaChangeInfo(memberGuid, affArea, drawOnMask).AsT1;
+
+            List<IChangeInfo> changes = new()
+            {
+                info,
+                new ComputedPropertyValue_ChangeInfo(brushOutputGuid, "VectorShape", true,
+                    brushOutputNode?.VectorShape.Value)
+            };
+
+            return changes;
         }
     }
 

+ 3 - 2
src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs

@@ -903,14 +903,15 @@ internal class DocumentUpdater
     private void ProcessComputedPropertyValue(ComputedPropertyValue_ChangeInfo info)
     {
         object finalValue = info.Value;
-        if (info.Value != null && !info.Value.GetType().IsValueType && info.Value is not string)
+        // TODO: Why to string???
+        /*if (info.Value != null && !info.Value.GetType().IsValueType && info.Value is not string)
         {
             bool valueToStringIsDefault = info.Value.GetType().FullName == info.Value.ToString();
             if (valueToStringIsDefault)
             {
                 finalValue = info.Value?.GetType().Name ?? finalValue;
             }
-        }
+        }*/
 
         NodeViewModel node = doc.StructureHelper.FindNode<NodeViewModel>(info.Node);
         INodePropertyHandler property;

+ 75 - 11
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/PenToolExecutor.cs

@@ -1,4 +1,5 @@
-using PixiEditor.ChangeableDocument.Actions;
+using Avalonia.Input;
+using PixiEditor.ChangeableDocument.Actions;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using Drawie.Backend.Core.ColorsImpl;
 using Drawie.Backend.Core.Vector;
@@ -9,7 +10,13 @@ using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.Models.Tools;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Brushes;
+using PixiEditor.ChangeableDocument.Changeables.Graph;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces.Shapes;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Brushes;
 using PixiEditor.Models.Controllers.InputDevice;
+using PixiEditor.ViewModels.Document.Nodes.Brushes;
+using PixiEditor.ViewModels.Nodes;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 #nullable enable
@@ -19,6 +26,7 @@ internal class PenToolExecutor : UpdateableChangeExecutor
     private Color color;
     public double ToolSize => penToolbar.ToolSize;
     public bool SquareBrush => penToolbar.PaintShape == PaintBrushShape.Square;
+    public override bool BlocksOtherActions => controller.LeftMousePressed;
 
     private bool drawOnMask;
     private bool pixelPerfect;
@@ -28,8 +36,10 @@ internal class PenToolExecutor : UpdateableChangeExecutor
     private bool transparentErase;
 
     private Guid brushOutputGuid = Guid.Empty;
+    private INodePropertyHandler vectorShapeInput;
 
     private IPenToolbar penToolbar;
+    private IPenToolHandler handler;
 
     public override ExecutionState Start()
     {
@@ -45,6 +55,7 @@ internal class PenToolExecutor : UpdateableChangeExecutor
         if (!drawOnMask && member is not ILayerHandler)
             return ExecutionState.Error;
 
+        handler = penTool;
         penToolbar = toolbar;
         guidValue = member.Id;
         color = colorsHandler.PrimaryColor;
@@ -58,29 +69,83 @@ internal class PenToolExecutor : UpdateableChangeExecutor
             colorsHandler.AddSwatch(new PaletteColor(color.R, color.G, color.B));
         }
 
-        brushOutputGuid = document.NodeGraphHandler.AllNodes.FirstOrDefault(x => x.InternalName == "PixiEditor.BrushOutput")?.Id ?? Guid.Empty;
+        brushOutputGuid =
+            document.NodeGraphHandler.AllNodes.FirstOrDefault(x => x.InternalName == "PixiEditor.BrushOutput")?.Id ??
+            Guid.Empty;
+        if (brushOutputGuid == Guid.Empty)
+            return ExecutionState.Error;
+
+        vectorShapeInput =
+            (document.NodeGraphHandler.NodeLookup[brushOutputGuid] as BrushOutputNodeViewModel).Inputs
+            .FirstOrDefault(x => x.PropertyName == "VectorShape");
 
         transparentErase = color.A == 0;
-        IAction? action = pixelPerfect switch
+        vectorShapeInput.UpdateComputedValue();
+        if (controller.LeftMousePressed)
         {
-            false => new LineBasedPen_Action(guidValue, color, controller!.LastPixelPosition, (float)ToolSize, transparentErase, antiAliasing, hardness, spacing, brushOutputGuid, drawOnMask, document!.AnimationHandler.ActiveFrameBindable, controller.LastPointerInfo),
-            true => new PixelPerfectPen_Action(guidValue, controller!.LastPixelPosition, color, drawOnMask, document!.AnimationHandler.ActiveFrameBindable)
-        };
-        internals!.ActionAccumulator.AddActions(action);
+            IAction? action = pixelPerfect switch
+            {
+                false => new LineBasedPen_Action(guidValue, color, controller!.LastPixelPosition, (float)ToolSize,
+                    transparentErase, antiAliasing, hardness, spacing, brushOutputGuid, drawOnMask,
+                    document!.AnimationHandler.ActiveFrameBindable, controller.LastPointerInfo),
+                true => new PixelPerfectPen_Action(guidValue, controller!.LastPixelPosition, color, drawOnMask,
+                    document!.AnimationHandler.ActiveFrameBindable)
+            };
+            internals!.ActionAccumulator.AddActions(action);
+        }
+
+        handler.FinalBrushShape = (vectorShapeInput.ComputedValue as IReadOnlyShapeVectorData)?.ToPath(true);
 
         return ExecutionState.Success;
     }
 
-    public override void OnPixelPositionChange(VecI pos, MouseOnCanvasEventArgs args)
+    public override void OnLeftMouseButtonDown(MouseOnCanvasEventArgs args)
     {
+        base.OnLeftMouseButtonDown(args);
         IAction? action = pixelPerfect switch
         {
-            false => new LineBasedPen_Action(guidValue, color, pos, (float)ToolSize, transparentErase, antiAliasing, hardness, spacing, brushOutputGuid, drawOnMask, document!.AnimationHandler.ActiveFrameBindable, controller.LastPointerInfo),
-            true => new PixelPerfectPen_Action(guidValue, pos, color, drawOnMask, document!.AnimationHandler.ActiveFrameBindable)
+            false => new LineBasedPen_Action(guidValue, color, controller!.LastPixelPosition, (float)ToolSize,
+                transparentErase, antiAliasing, hardness, spacing, brushOutputGuid, drawOnMask,
+                document!.AnimationHandler.ActiveFrameBindable, controller.LastPointerInfo),
+            true => new PixelPerfectPen_Action(guidValue, controller!.LastPixelPosition, color, drawOnMask,
+                document!.AnimationHandler.ActiveFrameBindable)
         };
         internals!.ActionAccumulator.AddActions(action);
     }
 
+    public override void OnPrecisePositionChange(MouseOnCanvasEventArgs args)
+    {
+        base.OnPrecisePositionChange(args);
+        if (!controller.LeftMousePressed)
+        {
+            vectorShapeInput.UpdateComputedValue();
+        }
+
+        handler.FinalBrushShape = (vectorShapeInput.ComputedValue as IReadOnlyShapeVectorData)?.ToPath(true);
+    }
+
+    public override void OnPixelPositionChange(VecI pos, MouseOnCanvasEventArgs args)
+    {
+        if (controller.LeftMousePressed)
+        {
+            IAction? action = pixelPerfect switch
+            {
+                false => new LineBasedPen_Action(guidValue, color, pos, (float)ToolSize, transparentErase, antiAliasing,
+                    hardness, spacing, brushOutputGuid, drawOnMask, document!.AnimationHandler.ActiveFrameBindable,
+                    controller.LastPointerInfo),
+                true => new PixelPerfectPen_Action(guidValue, pos, color, drawOnMask,
+                    document!.AnimationHandler.ActiveFrameBindable)
+            };
+            internals!.ActionAccumulator.AddActions(action);
+        }
+    }
+
+    public override void OnConvertedKeyDown(Key key)
+    {
+        base.OnConvertedKeyDown(key);
+        handler.FinalBrushShape = (vectorShapeInput.ComputedValue as IReadOnlyShapeVectorData)?.ToPath(true);
+    }
+
     public override void OnLeftMouseButtonUp(VecD argsPositionOnCanvas)
     {
         IAction? action = pixelPerfect switch
@@ -90,7 +155,6 @@ internal class PenToolExecutor : UpdateableChangeExecutor
         };
 
         internals!.ActionAccumulator.AddFinishedActions(action);
-        onEnded?.Invoke(this);
     }
 
     public override void ForceStop()

+ 4 - 1
src/PixiEditor/Models/Handlers/Tools/IPenToolHandler.cs

@@ -1,6 +1,9 @@
-namespace PixiEditor.Models.Handlers.Tools;
+using Drawie.Backend.Core.Vector;
+
+namespace PixiEditor.Models.Handlers.Tools;
 
 internal interface IPenToolHandler : IToolHandler
 {
     public bool PixelPerfectEnabled { get; }
+    VectorPath? FinalBrushShape { get; set; }
 }

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

@@ -1,4 +1,5 @@
 using Avalonia.Input;
+using Drawie.Backend.Core.Vector;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.ViewModels.Tools.ToolSettings.Toolbars;
@@ -8,7 +9,6 @@ namespace PixiEditor.ViewModels.Tools;
 
 internal abstract class ShapeTool : ToolViewModel, IShapeToolHandler
 {
-    public override BrushShape FinalBrushShape => BrushShape.Hidden;
 
     public override bool UsesColor => true;
 

+ 7 - 1
src/PixiEditor/ViewModels/Tools/ToolViewModel.cs

@@ -4,6 +4,7 @@ using System.Runtime.CompilerServices;
 using Avalonia.Input;
 using CommunityToolkit.Mvvm.ComponentModel;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Vector;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers.Toolbars;
 using PixiEditor.Models.Input;
@@ -18,6 +19,7 @@ namespace PixiEditor.ViewModels.Tools;
 internal abstract class ToolViewModel : ObservableObject, IToolHandler
 {
     private bool canBeUsedOnActiveLayerOnActiveLayer = true;
+    private VectorPath? brushShape;
     public bool IsTransient { get; set; } = false;
     public KeyCombination Shortcut { get; set; }
 
@@ -28,7 +30,11 @@ internal abstract class ToolViewModel : ObservableObject, IToolHandler
 
     public virtual string DefaultIcon => PixiPerfectIcons.Placeholder;
 
-    public virtual BrushShape FinalBrushShape => BrushShape.Square;
+    public VectorPath? FinalBrushShape
+    {
+        get => brushShape;
+        set => SetProperty(ref brushShape, value);
+    }
 
     public abstract Type[]? SupportedLayerTypes { get; }
 

+ 2 - 1
src/PixiEditor/ViewModels/Tools/Tools/BrightnessToolViewModel.cs

@@ -1,6 +1,7 @@
 using Avalonia.Input;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Vector;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers.Tools;
@@ -30,7 +31,7 @@ internal class BrightnessToolViewModel : ToolViewModel, IBrightnessToolHandler
     public override bool IsErasable => true;
     public override LocalizedString Tooltip => new LocalizedString("BRIGHTNESS_TOOL_TOOLTIP", Shortcut);
 
-    public override BrushShape FinalBrushShape => BrushShape == PaintBrushShape.Square ? Views.Overlays.BrushShapeOverlay.BrushShape.Square : Views.Overlays.BrushShapeOverlay.BrushShape.CirclePixelated;
+    //TODO: BrushShape == PaintBrushShape.Square ? Views.Overlays.BrushShapeOverlay.BrushShape.Square : Views.Overlays.BrushShapeOverlay.BrushShape.CirclePixelated;
 
     public override string DefaultIcon => PixiPerfectIcons.Sun;
 

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

@@ -1,6 +1,7 @@
 using System.ComponentModel;
 using Avalonia.Input;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Vector;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers.Tools;
@@ -28,7 +29,6 @@ internal class ColorPickerToolViewModel : ToolViewModel, IColorPickerHandler
     public override bool UsesColor => true;
 
     public override string ToolNameLocalizationKey => "COLOR_PICKER_TOOL";
-    public override BrushShape FinalBrushShape => BrushShape.Hidden;
 
     public override string DefaultIcon => PixiPerfectIcons.Picker;
 

+ 2 - 1
src/PixiEditor/ViewModels/Tools/Tools/EraserToolViewModel.cs

@@ -1,6 +1,7 @@
 using Avalonia.Input;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Vector;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers.Tools;
@@ -27,7 +28,7 @@ internal class EraserToolViewModel : ToolViewModel, IEraserToolHandler
     public override bool IsErasable => true;
 
     public override string ToolNameLocalizationKey => "ERASER_TOOL";
-    public override BrushShape FinalBrushShape => PaintShape == PaintBrushShape.Square ? BrushShape.Square : BrushShapeSetting;
+    //TODO: PaintShape == PaintBrushShape.Square ? BrushShape.Square : BrushShapeSetting;
     public override Type[]? SupportedLayerTypes { get; } = { typeof(IRasterLayerHandler) };
 
     public override string DefaultIcon => PixiPerfectIcons.Eraser;

+ 3 - 1
src/PixiEditor/ViewModels/Tools/Tools/FloodFillToolViewModel.cs

@@ -1,6 +1,7 @@
 using Avalonia.Input;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Vector;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers.Tools;
@@ -18,7 +19,8 @@ internal class FloodFillToolViewModel : ToolViewModel, IFloodFillToolHandler
     private readonly string defaultActionDisplay = "FLOOD_FILL_TOOL_ACTION_DISPLAY_DEFAULT";
 
     public override string ToolNameLocalizationKey => "FLOOD_FILL_TOOL";
-    public override BrushShape FinalBrushShape => BrushShape.Pixel;
+
+    //TODO: Brush Shape was Pixel
     public override Type[]? SupportedLayerTypes { get; } = { typeof(IRasterLayerHandler) };
 
     public override LocalizedString Tooltip => new("FLOOD_FILL_TOOL_TOOLTIP", Shortcut);

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

@@ -1,6 +1,7 @@
 using Avalonia.Input;
 using PixiEditor.ChangeableDocument.Enums;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Vector;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Handlers.Tools;
 using Drawie.Numerics;
@@ -50,8 +51,9 @@ internal class LassoToolViewModel : ToolViewModel, ILassoToolHandler
 
     public override string ToolNameLocalizationKey => "LASSO_TOOL";
     public override string DefaultIcon => PixiPerfectIcons.Lasso;
-    public override BrushShape FinalBrushShape => BrushShape.Pixel;
-    
+
+    //TODO: BrushShape.Pixel;
+
     public override Type[]? SupportedLayerTypes { get; } = null; // all layer types are supported
 
     [Settings.Enum("MODE_LABEL")]

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

@@ -1,6 +1,7 @@
 using Avalonia.Input;
 using PixiEditor.ChangeableDocument.Enums;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Vector;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers.Tools;
@@ -19,8 +20,7 @@ internal class MagicWandToolViewModel : ToolViewModel, IMagicWandToolHandler
     public override LocalizedString Tooltip => new LocalizedString("MAGIC_WAND_TOOL_TOOLTIP", Shortcut);
 
     public override string ToolNameLocalizationKey => "MAGIC_WAND_TOOL";
-    public override BrushShape FinalBrushShape => BrushShape.Pixel;
-    public override Type[]? SupportedLayerTypes { get; } = { typeof(IRasterLayerHandler) }; 
+    public override Type[]? SupportedLayerTypes { get; } = { typeof(IRasterLayerHandler) };
 
     [Settings.Enum("MODE_LABEL")]
     public SelectionMode SelectMode => GetValue<SelectionMode>();

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

@@ -3,6 +3,7 @@ using ChunkyImageLib.DataHolders;
 using PixiEditor.Models.DocumentModels;
 using PixiEditor.Models.Handlers;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Vector;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Handlers.Tools;
 using Drawie.Numerics;
@@ -49,7 +50,6 @@ internal class MoveToolViewModel : ToolViewModel, IMoveToolHandler
         set => SetValue(value);
     }
 
-    public override BrushShape FinalBrushShape => BrushShape.Hidden;
     public override Type[]? SupportedLayerTypes { get; } = null;
     public override Type LayerTypeToCreateOnEmptyUse { get; } = null;
     public override bool HideHighlight => true;

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

@@ -1,6 +1,7 @@
 using Avalonia;
 using Avalonia.Input;
 using Avalonia.Media.Imaging;
+using Drawie.Backend.Core.Vector;
 using PixiEditor.Helpers;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.Commands.Attributes.Commands;
@@ -14,7 +15,6 @@ namespace PixiEditor.ViewModels.Tools.Tools;
 internal class MoveViewportToolViewModel : ToolViewModel
 {
     public override string ToolNameLocalizationKey => "MOVE_VIEWPORT_TOOL";
-    public override BrushShape FinalBrushShape => BrushShape.Hidden;
     public override Type[]? SupportedLayerTypes { get; } = null;
     public override Type LayerTypeToCreateOnEmptyUse { get; } = null;
     public override bool HideHighlight => true;

+ 20 - 3
src/PixiEditor/ViewModels/Tools/Tools/PenToolViewModel.cs

@@ -1,4 +1,5 @@
 using Avalonia.Input;
+using Drawie.Backend.Core.Vector;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.Extensions.CommonApi.UserPreferences.Settings.PixiEditor;
 using PixiEditor.Models.Commands.Attributes.Commands;
@@ -22,9 +23,10 @@ namespace PixiEditor.ViewModels.Tools.Tools
 
         public override string ToolNameLocalizationKey => "PEN_TOOL";
 
-        public override BrushShape FinalBrushShape =>
+            /*
             PaintShape == PaintBrushShape.Square ? BrushShape.Square : BrushShapeSetting;
-        
+            */
+
         public override Type[]? SupportedLayerTypes { get; } = { typeof(IRasterLayerHandler) };
 
         public PenToolViewModel()
@@ -74,6 +76,21 @@ namespace PixiEditor.ViewModels.Tools.Tools
             ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UsePenTool();
         }
 
+        public void OnToolSelected(bool restoring)
+        {
+            ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UsePenTool();
+        }
+
+        public void OnPostUndoInlet()
+        {
+            ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UsePenTool();
+        }
+
+        public override void OnPostRedoInlet()
+        {
+            ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UsePenTool();
+        }
+
         private void SelectedToolChanged(object sender, SelectedToolEventArgs e)
         {
             if (e.NewTool == this && PixelPerfectEnabled)
@@ -147,7 +164,7 @@ namespace PixiEditor.ViewModels.Tools.Tools
                 }
             }
         }
-        
+
         private void BrushShapeChanged()
         {
             OnPropertyChanged(nameof(FinalBrushShape));

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

@@ -1,4 +1,5 @@
 using Avalonia.Input;
+using Drawie.Backend.Core.Vector;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.UI.Common.Fonts;
 using PixiEditor.UI.Common.Localization;
@@ -10,7 +11,6 @@ namespace PixiEditor.ViewModels.Tools.Tools;
 internal class RotateViewportToolViewModel : ToolViewModel
 {
     public override string ToolNameLocalizationKey => "ROTATE_VIEWPORT_TOOL";
-    public override BrushShape FinalBrushShape => BrushShape.Hidden;
     public override Type[]? SupportedLayerTypes { get; } = null; // null = all
     public override Type LayerTypeToCreateOnEmptyUse { get; } = null;
     public override bool HideHighlight => true;

+ 2 - 1
src/PixiEditor/ViewModels/Tools/Tools/SelectToolViewModel.cs

@@ -1,6 +1,7 @@
 using Avalonia.Input;
 using PixiEditor.ChangeableDocument.Enums;
 using Drawie.Backend.Core.Numerics;
+using Drawie.Backend.Core.Vector;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.Models.Position;
@@ -57,7 +58,7 @@ internal class SelectToolViewModel : ToolViewModel, ISelectToolHandler
     [Settings.Enum("SHAPE_LABEL")]
     public SelectionShape SelectShape => GetValue<SelectionShape>();
 
-    public override BrushShape FinalBrushShape => BrushShape.Pixel;
+    //TODO: was Pixel
     public override Type[]? SupportedLayerTypes { get; } = null;
 
     public override LocalizedString Tooltip => new LocalizedString("SELECT_TOOL_TOOLTIP", Shortcut);

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

@@ -1,4 +1,5 @@
 using Avalonia.Input;
+using Drawie.Backend.Core.Vector;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.UI.Common.Fonts;
 using PixiEditor.UI.Common.Localization;
@@ -19,7 +20,6 @@ internal class ZoomToolViewModel : ToolViewModel
     private string defaultActionDisplay = new LocalizedString("ZOOM_TOOL_ACTION_DISPLAY_DEFAULT");
 
     public override string ToolNameLocalizationKey => "ZOOM_TOOL";
-    public override BrushShape FinalBrushShape => BrushShape.Hidden;
     public override Type[]? SupportedLayerTypes { get; } = null;
 
     public override bool StopsLinkedToolOnUse => false;

+ 62 - 19
src/PixiEditor/Views/Overlays/BrushShapeOverlay/BrushShapeOverlay.cs

@@ -13,31 +13,32 @@ namespace PixiEditor.Views.Overlays.BrushShapeOverlay;
 #nullable enable
 internal class BrushShapeOverlay : Overlay
 {
-    public static readonly StyledProperty<int> BrushSizeProperty =
-        AvaloniaProperty.Register<BrushShapeOverlay, int>(nameof(BrushSize), defaultValue: 1);
+    public static readonly StyledProperty<float> BrushSizeProperty =
+        AvaloniaProperty.Register<BrushShapeOverlay, float>(nameof(BrushSize), defaultValue: 1);
 
-    public static readonly StyledProperty<BrushShape> BrushShapeProperty =
-        AvaloniaProperty.Register<BrushShapeOverlay, BrushShape>(nameof(BrushShape),
-            defaultValue: BrushShape.CirclePixelated);
 
     public static readonly StyledProperty<Scene> SceneProperty = AvaloniaProperty.Register<BrushShapeOverlay, Scene>(
         nameof(Scene));
 
-    public Scene Scene
+    public static readonly StyledProperty<VectorPath?> BrushShapeProperty =
+        AvaloniaProperty.Register<BrushShapeOverlay, VectorPath?>(
+            nameof(BrushShape));
+
+    public VectorPath? BrushShape
     {
-        get => GetValue(SceneProperty);
-        set => SetValue(SceneProperty, value);
+        get => GetValue(BrushShapeProperty);
+        set => SetValue(BrushShapeProperty, value);
     }
 
-    public BrushShape BrushShape
+    public Scene Scene
     {
-        get => (BrushShape)GetValue(BrushShapeProperty);
-        set => SetValue(BrushShapeProperty, value);
+        get => GetValue(SceneProperty);
+        set => SetValue(SceneProperty, value);
     }
 
-    public int BrushSize
+    public float BrushSize
     {
-        get => (int)GetValue(BrushSizeProperty);
+        get => (float)GetValue(BrushSizeProperty);
         set => SetValue(BrushSizeProperty, value);
     }
 
@@ -45,7 +46,7 @@ internal class BrushShapeOverlay : Overlay
     private VecD lastMousePos = new();
 
     private VectorPath threePixelCircle;
-    private int lastSize;
+    private float lastSize;
     private VectorPath lastNonTranslatedCircle;
 
 
@@ -62,7 +63,7 @@ internal class BrushShapeOverlay : Overlay
 
     protected override void OnOverlayPointerMoved(OverlayPointerArgs args)
     {
-        if (BrushShape == BrushShape.Hidden)
+        if (BrushShape == null)
             return;
 
         VecD rawPoint = args.Point;
@@ -78,7 +79,7 @@ internal class BrushShapeOverlay : Overlay
             (VecD)(new VecD(Math.Floor(lastMousePos.X), Math.Floor(lastMousePos.Y)) -
                    new VecD(BrushSize / 2, BrushSize / 2)),
             new VecD(BrushSize, BrushSize));
-        switch (BrushShape)
+        /*switch (BrushShape)
         {
             case BrushShape.Pixel:
                 paint.IsAntiAliased = false;
@@ -95,13 +96,55 @@ internal class BrushShapeOverlay : Overlay
             case BrushShape.CircleSmooth:
                 DrawCircleBrushShapeSmooth(targetCanvas, lastMousePos, BrushSize / 2f);
                 break;
+        }*/
+
+        if (BrushShape != null)
+        {
+            paint.IsAntiAliased = true;
+            targetCanvas.Save();
+            using var path = new VectorPath(BrushShape);
+            var rect = new RectI((VecI)lastMousePos - new VecI((int)(BrushSize / 2f)), new VecI((int)BrushSize));
+
+            path.Offset(rect.Center - path.Bounds.Center);
+
+            VecD scale = new VecD(rect.Size.X / (float)path.Bounds.Width, rect.Size.Y / (float)path.Bounds.Height);
+            if (scale.IsNaNOrInfinity())
+            {
+                scale = VecD.Zero;
+            }
+
+            VecD uniformScale = new VecD(Math.Min(scale.X, scale.Y));
+            path.Transform(Matrix3X3.CreateScale((float)uniformScale.X, (float)uniformScale.Y, (float)rect.Center.X,
+                (float)rect.Center.Y));
+
+            /*
+            if (brushNode.FitToStrokeSize.Value)
+            {
+                VecD scale = new VecD(rect.Size.X / (float)path.Bounds.Width, rect.Size.Y / (float)path.Bounds.Height);
+                if (scale.IsNaNOrInfinity())
+                {
+                    scale = VecD.Zero;
+                }
+
+                VecD uniformScale = new VecD(Math.Min(scale.X, scale.Y));
+                path.Transform(Matrix3X3.CreateScale((float)uniformScale.X, (float)uniformScale.Y, (float)rect.Center.X,
+                    (float)rect.Center.Y));
+            }
+
+            var pressure = brushNode.Pressure.Value;
+            Matrix3X3 pressureScale = Matrix3X3.CreateScale(pressure, pressure, (float)rect.Center.X,
+                (float)rect.Center.Y);
+            path.Transform(pressureScale);
+            */
+            targetCanvas.DrawPath(path, paint);
+            targetCanvas.Restore();
         }
     }
 
     private void DrawCircleBrushShape(Canvas drawingContext, RectD winRect)
     {
         paint.IsAntiAliased = false;
-        
+
         var rectI = new RectI((int)winRect.X, (int)winRect.Y, (int)winRect.Width, (int)winRect.Height);
         if (BrushSize < 3)
         {
@@ -139,9 +182,9 @@ internal class BrushShapeOverlay : Overlay
 
     private void DrawCircleBrushShapeSmooth(Canvas drawingContext, VecD lastMousePos, float radius)
     {
-        VecD center = lastMousePos; 
+        VecD center = lastMousePos;
         paint.IsAntiAliased = true;
-        
+
         drawingContext.DrawOval(new VecD(center.X, center.Y), new VecD(radius, radius),
             paint);
     }