Browse Source

Moved pointer overlay logic to brush overlay

Krzysztof Krysiński 1 month ago
parent
commit
52fe006471

+ 15 - 8
src/PixiEditor.ChangeableDocument/Changeables/Brushes/BrushEngine.cs

@@ -104,7 +104,7 @@ public class BrushEngine : IDisposable
     }
     }
 
 
 
 
-    public void ExecuteBrush(ChunkyImage target, BrushData brushData, VecD point, KeyFrameTime frameTime, ColorSpace cs,
+    public void ExecuteBrush(ChunkyImage? target, BrushData brushData, VecD point, KeyFrameTime frameTime, ColorSpace cs,
         SamplingOptions samplingOptions, PointerInfo pointerInfo, KeyboardInfo keyboardInfo, EditorData editorData)
         SamplingOptions samplingOptions, PointerInfo pointerInfo, KeyboardInfo keyboardInfo, EditorData editorData)
     {
     {
         var brushNode = brushData.BrushGraph?.AllNodes?.FirstOrDefault(x => x is BrushOutputNode) as BrushOutputNode;
         var brushNode = brushData.BrushGraph?.AllNodes?.FirstOrDefault(x => x is BrushOutputNode) as BrushOutputNode;
@@ -118,7 +118,7 @@ public class BrushEngine : IDisposable
             editorData);
             editorData);
     }
     }
 
 
-    private void ExecuteVectorShapeBrush(ChunkyImage target, BrushOutputNode brushNode, BrushData brushData, VecD point,
+    private void ExecuteVectorShapeBrush(ChunkyImage? target, BrushOutputNode brushNode, BrushData brushData, VecD point,
         KeyFrameTime frameTime,
         KeyFrameTime frameTime,
         ColorSpace colorSpace, SamplingOptions samplingOptions,
         ColorSpace colorSpace, SamplingOptions samplingOptions,
         PointerInfo pointerInfo, KeyboardInfo keyboardInfo, EditorData editorData)
         PointerInfo pointerInfo, KeyboardInfo keyboardInfo, EditorData editorData)
@@ -132,7 +132,7 @@ public class BrushEngine : IDisposable
             startPos = point;
             startPos = point;
             lastPos = point;
             lastPos = point;
             drawnOnce = true;
             drawnOnce = true;
-            target.SetBlendMode(imageBlendMode);
+            target?.SetBlendMode(imageBlendMode);
         }
         }
 
 
         float strokeWidth = brushData.StrokeWidth;
         float strokeWidth = brushData.StrokeWidth;
@@ -151,24 +151,24 @@ public class BrushEngine : IDisposable
 
 
         if (brushNode.AlwaysClear.Value)
         if (brushNode.AlwaysClear.Value)
         {
         {
-            target.EnqueueClear();
+            target?.EnqueueClear();
         }
         }
 
 
-        if (requiresSampleTexture && rect.Width > 0 && rect.Height > 0)
+        if (requiresSampleTexture && rect.Width > 0 && rect.Height > 0 && target != null)
         {
         {
             surfaceUnderRect = UpdateSurfaceUnderRect(target, (RectI)rect.RoundOutwards(), colorSpace,
             surfaceUnderRect = UpdateSurfaceUnderRect(target, (RectI)rect.RoundOutwards(), colorSpace,
                 brushNode.AllowSampleStacking.Value);
                 brushNode.AllowSampleStacking.Value);
         }
         }
 
 
-        if (requiresFullTexture)
+        if (requiresFullTexture && target != null)
         {
         {
             fullTexture = UpdateFullTexture(target, colorSpace, brushNode.AllowSampleStacking.Value);
             fullTexture = UpdateFullTexture(target, colorSpace, brushNode.AllowSampleStacking.Value);
         }
         }
 
 
         BrushRenderContext context = new BrushRenderContext(
         BrushRenderContext context = new BrushRenderContext(
             texture?.DrawingSurface.Canvas, frameTime, ChunkResolution.Full,
             texture?.DrawingSurface.Canvas, frameTime, ChunkResolution.Full,
-            brushNode.FitToStrokeSize.NonOverridenValue ? ((RectI)rect.RoundOutwards()).Size : target.CommittedSize,
-            target.CommittedSize,
+            brushNode.FitToStrokeSize.NonOverridenValue ? ((RectI)rect.RoundOutwards()).Size : target?.CommittedSize ?? VecI.Zero,
+            target?.CommittedSize ?? VecI.Zero,
             colorSpace, samplingOptions, brushData,
             colorSpace, samplingOptions, brushData,
             surfaceUnderRect, fullTexture, brushData.BrushGraph,
             surfaceUnderRect, fullTexture, brushData.BrushGraph,
             startPos, lastPos)
             startPos, lastPos)
@@ -180,6 +180,13 @@ public class BrushEngine : IDisposable
             KeyboardInfo = keyboardInfo
             KeyboardInfo = keyboardInfo
         };
         };
 
 
+        // Evaluate shape without painting if no target
+        if (target == null)
+        {
+            brushData.BrushGraph.Execute(brushNode, context);
+            using var shape = brushNode.VectorShape.Value.ToPath(true);
+            return;
+        }
 
 
         if (requiresSampleTexture && brushNode.VectorShape.Value != null)
         if (requiresSampleTexture && brushNode.VectorShape.Value != null)
         {
         {

+ 4 - 3
src/PixiEditor/Models/DocumentModels/ChangeExecutionController.cs

@@ -363,15 +363,16 @@ internal class ChangeExecutionController
             lastDirCalculationPoint = lastDirCalculationPoint.Lerp(currentPoint, 0.5f);
             lastDirCalculationPoint = lastDirCalculationPoint.Lerp(currentPoint, 0.5f);
         }
         }
 
 
+        VecD dir = lastDirCalculationPoint - currentPoint;
+        VecD vecDir = new VecD(dir.X, dir.Y);
+        VecD dirNormalized = vecDir.Length > 0 ? vecDir.Normalize() : lastPointerInfo.MovementDirection;
+
         float pressure = args.Properties.Pressure;
         float pressure = args.Properties.Pressure;
         if (args.PointerType == PointerType.Mouse)
         if (args.PointerType == PointerType.Mouse)
         {
         {
             pressure = args.Properties.Pressure > 0 ? 1 : 0;
             pressure = args.Properties.Pressure > 0 ? 1 : 0;
         }
         }
 
 
-        VecD dir = lastDirCalculationPoint - currentPoint;
-        VecD vecDir = new VecD(dir.X, dir.Y);
-        VecD dirNormalized = vecDir.Length > 0 ? vecDir.Normalize() : lastPointerInfo.MovementDirection;
         return new PointerInfo(currentPoint, pressure, args.Properties.Twist,
         return new PointerInfo(currentPoint, pressure, args.Properties.Twist,
             new VecD(args.Properties.XTilt, args.Properties.YTilt), dirNormalized);
             new VecD(args.Properties.XTilt, args.Properties.YTilt), dirNormalized);
     }
     }

+ 8 - 49
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/BrushBasedExecutor.cs

@@ -35,12 +35,10 @@ internal class BrushBasedExecutor<T> : BrushBasedExecutor where T : IBrushToolHa
 
 
 internal class BrushBasedExecutor : UpdateableChangeExecutor
 internal class BrushBasedExecutor : UpdateableChangeExecutor
 {
 {
-    public BrushData BrushData => brushData ??= GetBrushFromToolbar(BrushToolbar);
+    public BrushData BrushData => brushData ??= BrushToolbar.CreateBrushData();
     private BrushData? brushData;
     private BrushData? brushData;
     private Guid brushOutputGuid = Guid.Empty;
     private Guid brushOutputGuid = Guid.Empty;
     private BrushOutputNode? outputNode;
     private BrushOutputNode? outputNode;
-    private ChunkyImage previewImage = null!;
-    private ChangeableDocument.Changeables.Brushes.BrushEngine engine = new();
     private VecD lastSmoothed;
     private VecD lastSmoothed;
     private Stopwatch stopwatch = new Stopwatch();
     private Stopwatch stopwatch = new Stopwatch();
 
 
@@ -89,8 +87,6 @@ internal class BrushBasedExecutor : UpdateableChangeExecutor
         antiAliasing = toolbar.AntiAliasing;
         antiAliasing = toolbar.AntiAliasing;
         this.colorsHandler = colorsHandler;
         this.colorsHandler = colorsHandler;
 
 
-        previewImage = new ChunkyImage(new VecI(1), ColorSpace.CreateSrgb());
-
         UpdateBrushNodes();
         UpdateBrushNodes();
 
 
         if (controller.LeftMousePressed)
         if (controller.LeftMousePressed)
@@ -98,8 +94,6 @@ internal class BrushBasedExecutor : UpdateableChangeExecutor
             EnqueueDrawActions();
             EnqueueDrawActions();
         }
         }
 
 
-        UpdateBrushOverlay(controller.LastPrecisePosition);
-
         return ExecutionState.Success;
         return ExecutionState.Success;
     }
     }
 
 
@@ -145,23 +139,6 @@ internal class BrushBasedExecutor : UpdateableChangeExecutor
             BrushData.BrushGraph.AllNodes.FirstOrDefault(x => x.Id == brushOutputGuid) as BrushOutputNode;
             BrushData.BrushGraph.AllNodes.FirstOrDefault(x => x.Id == brushOutputGuid) as BrushOutputNode;
     }
     }
 
 
-    private BrushData GetBrushFromToolbar(IBrushToolbar toolbar)
-    {
-        Brush? brush = toolbar.Brush;
-        if (brush == null)
-        {
-            return new BrushData();
-        }
-
-        var pipe = toolbar.Brush.Document.ShareGraph();
-        var data = new BrushData(pipe.TryAccessData())
-        {
-            AntiAliasing = toolbar.AntiAliasing, StrokeWidth = (float)toolbar.ToolSize
-        };
-        pipe.Dispose();
-        return data;
-    }
-
     public override void OnLeftMouseButtonDown(MouseOnCanvasEventArgs args)
     public override void OnLeftMouseButtonDown(MouseOnCanvasEventArgs args)
     {
     {
         base.OnLeftMouseButtonDown(args);
         base.OnLeftMouseButtonDown(args);
@@ -171,54 +148,37 @@ internal class BrushBasedExecutor : UpdateableChangeExecutor
     public override void OnPrecisePositionChange(MouseOnCanvasEventArgs args)
     public override void OnPrecisePositionChange(MouseOnCanvasEventArgs args)
     {
     {
         base.OnPrecisePositionChange(args);
         base.OnPrecisePositionChange(args);
-        if (!controller.LeftMousePressed)
-        {
-            ExecuteBrush();
-        }
-        else
+        if (controller.LeftMousePressed)
         {
         {
             EnqueueDrawActions();
             EnqueueDrawActions();
         }
         }
-
-        UpdateBrushOverlay(args.PositionOnCanvas);
-    }
-
-    private void UpdateBrushOverlay(VecD pos)
-    {
-        if (!brushData.HasValue || brushData.Value.BrushGraph == null) return;
-
-        handler.FinalBrushShape = engine.EvaluateShape(pos, brushData.Value);
-    }
-
-    private void ExecuteBrush()
-    {
-        engine.ExecuteBrush(previewImage, BrushData, controller.LastPrecisePosition,
-            document.AnimationHandler.ActiveFrameTime,
-            ColorSpace.CreateSrgb(), SamplingOptions.Default, controller.LastPointerInfo with { Pressure = 1f}, controller.LastKeyboardInfo,
-            controller.EditorData);
     }
     }
 
 
     public override void OnConvertedKeyDown(Key key)
     public override void OnConvertedKeyDown(Key key)
     {
     {
         base.OnConvertedKeyDown(key);
         base.OnConvertedKeyDown(key);
+        /*
         UpdateBrushOverlay(controller.LastPrecisePosition);
         UpdateBrushOverlay(controller.LastPrecisePosition);
+    */
     }
     }
 
 
     public override void OnSettingsChanged(string name, object value)
     public override void OnSettingsChanged(string name, object value)
     {
     {
         if (name == nameof(BrushToolbar.Brush))
         if (name == nameof(BrushToolbar.Brush))
         {
         {
-            brushData = GetBrushFromToolbar(BrushToolbar);
+            brushData = BrushToolbar.CreateBrushData();
             UpdateBrushNodes();
             UpdateBrushNodes();
         }
         }
 
 
         if (name is nameof(IBrushToolbar.ToolSize) or nameof(IBrushToolbar.AntiAliasing))
         if (name is nameof(IBrushToolbar.ToolSize) or nameof(IBrushToolbar.AntiAliasing))
         {
         {
-            brushData = GetBrushFromToolbar(BrushToolbar);
+            brushData = BrushToolbar.CreateBrushData();
         }
         }
 
 
+        /*
         ExecuteBrush();
         ExecuteBrush();
         UpdateBrushOverlay(controller.LastPrecisePosition);
         UpdateBrushOverlay(controller.LastPrecisePosition);
+    */
     }
     }
 
 
 
 
@@ -235,6 +195,5 @@ internal class BrushBasedExecutor : UpdateableChangeExecutor
     public override void ForceStop()
     public override void ForceStop()
     {
     {
         EnqueueEndDraw();
         EnqueueEndDraw();
-        engine.Dispose();
     }
     }
 }
 }

+ 4 - 1
src/PixiEditor/Models/Handlers/Toolbars/IBrushToolbar.cs

@@ -1,4 +1,5 @@
-using PixiEditor.Models.BrushEngine;
+using PixiEditor.ChangeableDocument.Changeables.Brushes;
+using PixiEditor.Models.BrushEngine;
 using PixiEditor.Views.Overlays.BrushShapeOverlay;
 using PixiEditor.Views.Overlays.BrushShapeOverlay;
 
 
 namespace PixiEditor.Models.Handlers.Toolbars;
 namespace PixiEditor.Models.Handlers.Toolbars;
@@ -7,4 +8,6 @@ internal interface IBrushToolbar : IToolbar, IToolSizeToolbar
 {
 {
     public bool AntiAliasing { get; set; }
     public bool AntiAliasing { get; set; }
     public Brush Brush { get; set; }
     public Brush Brush { get; set; }
+    public BrushData CreateBrushData();
+    public BrushData LastBrushData { get; }
 }
 }

+ 0 - 1
src/PixiEditor/Models/Handlers/Tools/IBrushToolHandler.cs

@@ -5,7 +5,6 @@ namespace PixiEditor.Models.Handlers.Tools;
 
 
 internal interface IBrushToolHandler : IToolHandler
 internal interface IBrushToolHandler : IToolHandler
 {
 {
-    VectorPath? FinalBrushShape { get; set; }
     public bool IsCustomBrushTool { get; }
     public bool IsCustomBrushTool { get; }
     KeyCombination? DefaultShortcut { get; }
     KeyCombination? DefaultShortcut { get; }
 }
 }

+ 6 - 0
src/PixiEditor/ViewModels/SubViewModels/ToolsViewModel.cs

@@ -99,6 +99,11 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
         get => ActiveTool?.Toolbar as IToolSizeToolbar;
         get => ActiveTool?.Toolbar as IToolSizeToolbar;
     }
     }
 
 
+    public IBrushToolbar? ActiveBrushToolbar
+    {
+        get => ActiveTool?.Toolbar as IBrushToolbar;
+    }
+
     private IToolHandler? activeTool;
     private IToolHandler? activeTool;
 
 
     public IToolHandler? ActiveTool
     public IToolHandler? ActiveTool
@@ -108,6 +113,7 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
         {
         {
             SetProperty(ref activeTool, value);
             SetProperty(ref activeTool, value);
             OnPropertyChanged(nameof(ActiveBasicToolbar));
             OnPropertyChanged(nameof(ActiveBasicToolbar));
+            OnPropertyChanged(nameof(ActiveBrushToolbar));
         }
         }
     }
     }
 
 

+ 34 - 1
src/PixiEditor/ViewModels/Tools/ToolSettings/Toolbars/BrushToolbar.cs

@@ -1,4 +1,6 @@
-using Drawie.Backend.Core.Surfaces.PaintImpl;
+using System.ComponentModel;
+using Drawie.Backend.Core.Surfaces.PaintImpl;
+using PixiEditor.ChangeableDocument.Changeables.Brushes;
 using PixiEditor.Models.BrushEngine;
 using PixiEditor.Models.BrushEngine;
 using PixiEditor.Models.Handlers.Toolbars;
 using PixiEditor.Models.Handlers.Toolbars;
 using PixiEditor.ViewModels.Tools.ToolSettings.Settings;
 using PixiEditor.ViewModels.Tools.ToolSettings.Settings;
@@ -26,6 +28,23 @@ internal class BrushToolbar : Toolbar, IBrushToolbar
         set => GetSetting<BrushSettingViewModel>(nameof(Brush)).Value = value;
         set => GetSetting<BrushSettingViewModel>(nameof(Brush)).Value = value;
     }
     }
 
 
+    public BrushData CreateBrushData()
+    {
+        Brush? brush = Brush;
+        if (brush == null)
+        {
+            return new BrushData();
+        }
+
+        var pipe = Brush.Document.ShareGraph();
+        var data = new BrushData(pipe.TryAccessData()) { AntiAliasing = AntiAliasing, StrokeWidth = (float)ToolSize };
+
+        pipe.Dispose();
+        return data;
+    }
+
+    public BrushData LastBrushData { get; private set; } = new BrushData();
+
     public override void OnLoadedSettings()
     public override void OnLoadedSettings()
     {
     {
         OnPropertyChanged(nameof(ToolSize));
         OnPropertyChanged(nameof(ToolSize));
@@ -38,5 +57,19 @@ internal class BrushToolbar : Toolbar, IBrushToolbar
         setting.ValueChanged += (_, _) => OnPropertyChanged(nameof(ToolSize));
         setting.ValueChanged += (_, _) => OnPropertyChanged(nameof(ToolSize));
         AddSetting(setting);
         AddSetting(setting);
         AddSetting(new BrushSettingViewModel(nameof(Brush), "BRUSH_SETTING") { IsExposed = true });
         AddSetting(new BrushSettingViewModel(nameof(Brush), "BRUSH_SETTING") { IsExposed = true });
+
+        foreach (var aSetting in Settings)
+        {
+            if (aSetting.Name == "Brush" || aSetting.Name == "AntiAliasing" || aSetting.Name == "ToolSize")
+            {
+                aSetting.ValueChanged += SettingOnValueChanged;
+            }
+        }
+    }
+
+    private void SettingOnValueChanged(object? sender, SettingValueChangedEventArgs<object> e)
+    {
+        LastBrushData = CreateBrushData();
+        OnPropertyChanged(nameof(LastBrushData));
     }
     }
 }
 }

+ 15 - 9
src/PixiEditor/Views/Main/ViewportControls/ViewportOverlays.cs

@@ -456,14 +456,9 @@ internal class ViewportOverlays
             Source = Viewport, Path = "IsOverCanvas", Mode = BindingMode.OneWay
             Source = Viewport, Path = "IsOverCanvas", Mode = BindingMode.OneWay
         };
         };
 
 
-        Binding brushSizeBinding = new()
+        Binding brushDataBinding = new()
         {
         {
-            Source = ViewModelMain.Current.ToolsSubViewModel, Path = "ActiveBasicToolbar.ToolSize", Mode = BindingMode.OneWay
-        };
-
-        Binding brushShapeBinding = new()
-        {
-            Source = ViewModelMain.Current.ToolsSubViewModel, Path = "ActiveTool.FinalBrushShape", Mode = BindingMode.OneWay
+            Source = ViewModelMain.Current.ToolsSubViewModel, Path = "ActiveBrushToolbar.LastBrushData", Mode = BindingMode.OneWay
         };
         };
 
 
         MultiBinding isVisibleMultiBinding = new()
         MultiBinding isVisibleMultiBinding = new()
@@ -477,9 +472,20 @@ internal class ViewportOverlays
             }
             }
         };
         };
 
 
+        Binding activeFrameTimeBidning = new()
+        {
+            Source = ViewModelMain.Current.DocumentManagerSubViewModel, Path = "ActiveDocument.AnimationDataViewModel.ActiveFrameTime", Mode = BindingMode.OneWay
+        };
+
+        Binding editorDataBinding = new()
+        {
+            Source = ViewModelMain.Current, Path = "GetEditorData", Mode = BindingMode.OneWay
+        };
+
         brushShapeOverlay.Bind(Visual.IsVisibleProperty, isVisibleMultiBinding);
         brushShapeOverlay.Bind(Visual.IsVisibleProperty, isVisibleMultiBinding);
-        brushShapeOverlay.Bind(BrushShapeOverlay.BrushSizeProperty, brushSizeBinding);
-        brushShapeOverlay.Bind(BrushShapeOverlay.BrushShapeProperty, brushShapeBinding);
+        brushShapeOverlay.Bind(BrushShapeOverlay.BrushDataProperty, brushDataBinding);
+        brushShapeOverlay.Bind(BrushShapeOverlay.ActiveFrameTimeProperty, activeFrameTimeBidning);
+        brushShapeOverlay.Bind(BrushShapeOverlay.EditorDataProperty, editorDataBinding);
     }
     }
 
 
     private void BindTextOverlay()
     private void BindTextOverlay()

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

@@ -1,10 +1,15 @@
 using Avalonia;
 using Avalonia;
-using ChunkyImageLib.Operations;
-using Drawie.Backend.Core.Numerics;
+using Avalonia.Input;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Vector;
 using Drawie.Backend.Core.Vector;
 using PixiEditor.Extensions.UI.Overlays;
 using PixiEditor.Extensions.UI.Overlays;
 using Drawie.Numerics;
 using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.ChangeableDocument.Changeables.Brushes;
+using PixiEditor.ChangeableDocument.Rendering.ContextData;
+using PixiEditor.UI.Common.Extensions;
 using PixiEditor.Views.Rendering;
 using PixiEditor.Views.Rendering;
 using Canvas = Drawie.Backend.Core.Surfaces.Canvas;
 using Canvas = Drawie.Backend.Core.Surfaces.Canvas;
 using Colors = Drawie.Backend.Core.ColorsImpl.Colors;
 using Colors = Drawie.Backend.Core.ColorsImpl.Colors;
@@ -13,10 +18,6 @@ namespace PixiEditor.Views.Overlays.BrushShapeOverlay;
 #nullable enable
 #nullable enable
 internal class BrushShapeOverlay : Overlay
 internal class BrushShapeOverlay : Overlay
 {
 {
-    public static readonly StyledProperty<float> BrushSizeProperty =
-        AvaloniaProperty.Register<BrushShapeOverlay, float>(nameof(BrushSize), defaultValue: 1);
-
-
     public static readonly StyledProperty<Scene> SceneProperty = AvaloniaProperty.Register<BrushShapeOverlay, Scene>(
     public static readonly StyledProperty<Scene> SceneProperty = AvaloniaProperty.Register<BrushShapeOverlay, Scene>(
         nameof(Scene));
         nameof(Scene));
 
 
@@ -24,6 +25,36 @@ internal class BrushShapeOverlay : Overlay
         AvaloniaProperty.Register<BrushShapeOverlay, VectorPath?>(
         AvaloniaProperty.Register<BrushShapeOverlay, VectorPath?>(
             nameof(BrushShape));
             nameof(BrushShape));
 
 
+    public static readonly StyledProperty<KeyFrameTime> ActiveFrameTimeProperty =
+        AvaloniaProperty.Register<BrushShapeOverlay, KeyFrameTime>(
+            nameof(ActiveFrameTime));
+
+    public static readonly StyledProperty<Func<EditorData>> EditorDataProperty =
+        AvaloniaProperty.Register<BrushShapeOverlay, Func<EditorData>>(
+            nameof(EditorData));
+
+    public Func<EditorData> EditorData
+    {
+        get => GetValue(EditorDataProperty);
+        set => SetValue(EditorDataProperty, value);
+    }
+
+    public KeyFrameTime ActiveFrameTime
+    {
+        get => GetValue(ActiveFrameTimeProperty);
+        set => SetValue(ActiveFrameTimeProperty, value);
+    }
+
+    public static readonly StyledProperty<BrushData> BrushDataProperty =
+        AvaloniaProperty.Register<BrushShapeOverlay, BrushData>(
+            nameof(BrushData));
+
+    public BrushData BrushData
+    {
+        get => GetValue(BrushDataProperty);
+        set => SetValue(BrushDataProperty, value);
+    }
+
     public VectorPath? BrushShape
     public VectorPath? BrushShape
     {
     {
         get => GetValue(BrushShapeProperty);
         get => GetValue(BrushShapeProperty);
@@ -36,22 +67,20 @@ internal class BrushShapeOverlay : Overlay
         set => SetValue(SceneProperty, value);
         set => SetValue(SceneProperty, value);
     }
     }
 
 
-    public float BrushSize
-    {
-        get => (float)GetValue(BrushSizeProperty);
-        set => SetValue(BrushSizeProperty, value);
-    }
-
     private Paint paint = new Paint() { Color = Colors.LightGray, StrokeWidth = 1, Style = PaintStyle.Stroke };
     private Paint paint = new Paint() { Color = Colors.LightGray, StrokeWidth = 1, Style = PaintStyle.Stroke };
-    private VecD lastMousePos = new();
 
 
+    private VecD lastDirCalculationPoint;
     private float lastSize;
     private float lastSize;
-    private VectorPath lastNonTranslatedCircle;
+    private PointerInfo lastPointerInfo;
 
 
+    private ChangeableDocument.Changeables.Brushes.BrushEngine engine = new();
 
 
     static BrushShapeOverlay()
     static BrushShapeOverlay()
     {
     {
-        AffectsOverlayRender(BrushShapeProperty, BrushSizeProperty);
+        AffectsOverlayRender(BrushShapeProperty, BrushDataProperty, ActiveFrameTimeProperty, EditorDataProperty);
+        BrushDataProperty.Changed.AddClassHandler<BrushShapeOverlay>((overlay, args) => UpdateBrush(args));
+        ActiveFrameTimeProperty.Changed.AddClassHandler<BrushShapeOverlay>((overlay, args) => UpdateBrush(args));
+        EditorDataProperty.Changed.AddClassHandler<BrushShapeOverlay>((overlay, args) => UpdateBrush(args));
     }
     }
 
 
     public BrushShapeOverlay()
     public BrushShapeOverlay()
@@ -59,16 +88,56 @@ internal class BrushShapeOverlay : Overlay
         IsHitTestVisible = false;
         IsHitTestVisible = false;
     }
     }
 
 
+    private static void UpdateBrush(AvaloniaPropertyChangedEventArgs args)
+    {
+        BrushShapeOverlay overlay = args.Sender as BrushShapeOverlay;
+        if (overlay == null) return;
+        overlay.UpdateBrushShape(overlay.lastDirCalculationPoint);
+        overlay.Refresh();
+    }
+
+    private void ExecuteBrush(VecD pos)
+    {
+        if (VecD.Distance(lastDirCalculationPoint, pos) > 1)
+        {
+            lastDirCalculationPoint = lastDirCalculationPoint.Lerp(pos, 0.5f);
+        }
+
+        VecD dir = lastDirCalculationPoint - pos;
+        VecD vecDir = new VecD(dir.X, dir.Y);
+        VecD dirNormalized = vecDir.Length > 0 ? vecDir.Normalize() : lastPointerInfo.MovementDirection;
+
+        PointerInfo pointer = new PointerInfo(pos, 1, 0, VecD.Zero, dirNormalized);
+
+        engine.ExecuteBrush(null, BrushData, pos, ActiveFrameTime,
+            ColorSpace.CreateSrgb(), SamplingOptions.Default, pointer, new KeyboardInfo(), EditorData?.Invoke() ?? new EditorData(Colors.White, Colors.Black));
+    }
+
     protected override void OnOverlayPointerMoved(OverlayPointerArgs args)
     protected override void OnOverlayPointerMoved(OverlayPointerArgs args)
     {
     {
-        if (BrushShape == null)
-            return;
+        if (!args.Properties.IsLeftButtonPressed && BrushData.BrushGraph != null)
+        {
+            ExecuteBrush(args.Point);
+        }
 
 
-        VecD rawPoint = args.Point;
-        lastMousePos = rawPoint;
+        UpdateBrushShape(args.Point);
+
+        Refresh();
+    }
+
+    protected override void OnKeyPressed(KeyEventArgs args)
+    {
+        UpdateBrushShape(lastDirCalculationPoint);
         Refresh();
         Refresh();
     }
     }
 
 
+    private void UpdateBrushShape(VecD pos)
+    {
+        if (BrushData.BrushGraph == null) return;
+
+        BrushShape = engine.EvaluateShape(pos, BrushData);
+    }
+
     protected override void OnRenderOverlay(Canvas context, RectD canvasBounds) => Render(context);
     protected override void OnRenderOverlay(Canvas context, RectD canvasBounds) => Render(context);
 
 
     public void Render(Canvas targetCanvas)
     public void Render(Canvas targetCanvas)