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)
     {
         var brushNode = brushData.BrushGraph?.AllNodes?.FirstOrDefault(x => x is BrushOutputNode) as BrushOutputNode;
@@ -118,7 +118,7 @@ public class BrushEngine : IDisposable
             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,
         ColorSpace colorSpace, SamplingOptions samplingOptions,
         PointerInfo pointerInfo, KeyboardInfo keyboardInfo, EditorData editorData)
@@ -132,7 +132,7 @@ public class BrushEngine : IDisposable
             startPos = point;
             lastPos = point;
             drawnOnce = true;
-            target.SetBlendMode(imageBlendMode);
+            target?.SetBlendMode(imageBlendMode);
         }
 
         float strokeWidth = brushData.StrokeWidth;
@@ -151,24 +151,24 @@ public class BrushEngine : IDisposable
 
         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,
                 brushNode.AllowSampleStacking.Value);
         }
 
-        if (requiresFullTexture)
+        if (requiresFullTexture && target != null)
         {
             fullTexture = UpdateFullTexture(target, colorSpace, brushNode.AllowSampleStacking.Value);
         }
 
         BrushRenderContext context = new BrushRenderContext(
             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,
             surfaceUnderRect, fullTexture, brushData.BrushGraph,
             startPos, lastPos)
@@ -180,6 +180,13 @@ public class BrushEngine : IDisposable
             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)
         {

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

@@ -363,15 +363,16 @@ internal class ChangeExecutionController
             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;
         if (args.PointerType == PointerType.Mouse)
         {
             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,
             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
 {
-    public BrushData BrushData => brushData ??= GetBrushFromToolbar(BrushToolbar);
+    public BrushData BrushData => brushData ??= BrushToolbar.CreateBrushData();
     private BrushData? brushData;
     private Guid brushOutputGuid = Guid.Empty;
     private BrushOutputNode? outputNode;
-    private ChunkyImage previewImage = null!;
-    private ChangeableDocument.Changeables.Brushes.BrushEngine engine = new();
     private VecD lastSmoothed;
     private Stopwatch stopwatch = new Stopwatch();
 
@@ -89,8 +87,6 @@ internal class BrushBasedExecutor : UpdateableChangeExecutor
         antiAliasing = toolbar.AntiAliasing;
         this.colorsHandler = colorsHandler;
 
-        previewImage = new ChunkyImage(new VecI(1), ColorSpace.CreateSrgb());
-
         UpdateBrushNodes();
 
         if (controller.LeftMousePressed)
@@ -98,8 +94,6 @@ internal class BrushBasedExecutor : UpdateableChangeExecutor
             EnqueueDrawActions();
         }
 
-        UpdateBrushOverlay(controller.LastPrecisePosition);
-
         return ExecutionState.Success;
     }
 
@@ -145,23 +139,6 @@ internal class BrushBasedExecutor : UpdateableChangeExecutor
             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)
     {
         base.OnLeftMouseButtonDown(args);
@@ -171,54 +148,37 @@ internal class BrushBasedExecutor : UpdateableChangeExecutor
     public override void OnPrecisePositionChange(MouseOnCanvasEventArgs args)
     {
         base.OnPrecisePositionChange(args);
-        if (!controller.LeftMousePressed)
-        {
-            ExecuteBrush();
-        }
-        else
+        if (controller.LeftMousePressed)
         {
             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)
     {
         base.OnConvertedKeyDown(key);
+        /*
         UpdateBrushOverlay(controller.LastPrecisePosition);
+    */
     }
 
     public override void OnSettingsChanged(string name, object value)
     {
         if (name == nameof(BrushToolbar.Brush))
         {
-            brushData = GetBrushFromToolbar(BrushToolbar);
+            brushData = BrushToolbar.CreateBrushData();
             UpdateBrushNodes();
         }
 
         if (name is nameof(IBrushToolbar.ToolSize) or nameof(IBrushToolbar.AntiAliasing))
         {
-            brushData = GetBrushFromToolbar(BrushToolbar);
+            brushData = BrushToolbar.CreateBrushData();
         }
 
+        /*
         ExecuteBrush();
         UpdateBrushOverlay(controller.LastPrecisePosition);
+    */
     }
 
 
@@ -235,6 +195,5 @@ internal class BrushBasedExecutor : UpdateableChangeExecutor
     public override void ForceStop()
     {
         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;
 
 namespace PixiEditor.Models.Handlers.Toolbars;
@@ -7,4 +8,6 @@ internal interface IBrushToolbar : IToolbar, IToolSizeToolbar
 {
     public bool AntiAliasing { 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
 {
-    VectorPath? FinalBrushShape { get; set; }
     public bool IsCustomBrushTool { 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;
     }
 
+    public IBrushToolbar? ActiveBrushToolbar
+    {
+        get => ActiveTool?.Toolbar as IBrushToolbar;
+    }
+
     private IToolHandler? activeTool;
 
     public IToolHandler? ActiveTool
@@ -108,6 +113,7 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
         {
             SetProperty(ref activeTool, value);
             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.Handlers.Toolbars;
 using PixiEditor.ViewModels.Tools.ToolSettings.Settings;
@@ -26,6 +28,23 @@ internal class BrushToolbar : Toolbar, IBrushToolbar
         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()
     {
         OnPropertyChanged(nameof(ToolSize));
@@ -38,5 +57,19 @@ internal class BrushToolbar : Toolbar, IBrushToolbar
         setting.ValueChanged += (_, _) => OnPropertyChanged(nameof(ToolSize));
         AddSetting(setting);
         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
         };
 
-        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()
@@ -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(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()

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

@@ -1,10 +1,15 @@
 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.Vector;
 using PixiEditor.Extensions.UI.Overlays;
 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 Canvas = Drawie.Backend.Core.Surfaces.Canvas;
 using Colors = Drawie.Backend.Core.ColorsImpl.Colors;
@@ -13,10 +18,6 @@ namespace PixiEditor.Views.Overlays.BrushShapeOverlay;
 #nullable enable
 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>(
         nameof(Scene));
 
@@ -24,6 +25,36 @@ internal class BrushShapeOverlay : Overlay
         AvaloniaProperty.Register<BrushShapeOverlay, VectorPath?>(
             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
     {
         get => GetValue(BrushShapeProperty);
@@ -36,22 +67,20 @@ internal class BrushShapeOverlay : Overlay
         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 VecD lastMousePos = new();
 
+    private VecD lastDirCalculationPoint;
     private float lastSize;
-    private VectorPath lastNonTranslatedCircle;
+    private PointerInfo lastPointerInfo;
 
+    private ChangeableDocument.Changeables.Brushes.BrushEngine engine = new();
 
     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()
@@ -59,16 +88,56 @@ internal class BrushShapeOverlay : Overlay
         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)
     {
-        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();
     }
 
+    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);
 
     public void Render(Canvas targetCanvas)