Browse Source

BrushViewModel

Krzysztof Krysiński 3 weeks ago
parent
commit
d872047e42

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

@@ -106,7 +106,8 @@ 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?.LookupNode(brushData.TargetBrushNodeId) as BrushOutputNode;
         var brushNode = brushData.BrushGraph?.LookupNode(brushData.TargetBrushNodeId) as BrushOutputNode;
@@ -170,7 +171,9 @@ public class BrushEngine : IDisposable
 
 
         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 ?? VecI.Zero,
+            brushNode.FitToStrokeSize.NonOverridenValue
+                ? ((RectI)rect.RoundOutwards()).Size
+                : target?.CommittedSize ?? VecI.Zero,
             target?.CommittedSize ?? VecI.Zero,
             target?.CommittedSize ?? VecI.Zero,
             colorSpace, samplingOptions, brushData,
             colorSpace, samplingOptions, brushData,
             surfaceUnderRect, fullTexture, brushData.BrushGraph,
             surfaceUnderRect, fullTexture, brushData.BrushGraph,
@@ -187,6 +190,9 @@ public class BrushEngine : IDisposable
         if (target == null)
         if (target == null)
         {
         {
             brushData.BrushGraph.Execute(brushNode, context);
             brushData.BrushGraph.Execute(brushNode, context);
+            if (brushNode.VectorShape.Value == null)
+                return;
+
             using var shape = brushNode.VectorShape.Value.ToPath(true);
             using var shape = brushNode.VectorShape.Value.ToPath(true);
             return;
             return;
         }
         }
@@ -441,7 +447,8 @@ public class BrushEngine : IDisposable
     {
     {
         if (fitToStrokeSize)
         if (fitToStrokeSize)
         {
         {
-            VecD scale = new VecD(rect.Size.X / (float)path.TightBounds.Width, rect.Size.Y / (float)path.TightBounds.Height);
+            VecD scale = new VecD(rect.Size.X / (float)path.TightBounds.Width,
+                rect.Size.Y / (float)path.TightBounds.Height);
             if (scale.IsNaNOrInfinity())
             if (scale.IsNaNOrInfinity())
             {
             {
                 scale = VecD.Zero;
                 scale = VecD.Zero;

+ 31 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Blackboard.cs

@@ -1,4 +1,6 @@
-namespace PixiEditor.ChangeableDocument.Changeables.Graph;
+using PixiEditor.Common;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph;
 
 
 public class Blackboard : IReadOnlyBlackboard
 public class Blackboard : IReadOnlyBlackboard
 {
 {
@@ -50,6 +52,34 @@ public class Blackboard : IReadOnlyBlackboard
         variable.Name = newName;
         variable.Name = newName;
         variables[newName] = variable;
         variables[newName] = variable;
     }
     }
+
+    public int GetCacheHash()
+    {
+        HashCode hash = new HashCode();
+        hash.Add(variables.Count);
+        foreach (var variable in variables.Values)
+        {
+            hash.Add(variable.Name);
+            hash.Add(variable.Type);
+            if (variable.Value != null)
+            {
+                if(variable.Value is ICacheable cacheable)
+                {
+                    hash.Add(cacheable.GetCacheHash());
+                }
+                else
+                {
+                    hash.Add(variable.Value.GetHashCode());
+                }
+            }
+
+            hash.Add(variable.Unit);
+            hash.Add(variable.Min);
+            hash.Add(variable.Max);
+        }
+
+        return hash.ToHashCode();
+    }
 }
 }
 
 
 public class Variable : IReadOnlyVariable
 public class Variable : IReadOnlyVariable

+ 2 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/IReadOnlyBlackboard.cs

@@ -1,8 +1,9 @@
 using System.Collections;
 using System.Collections;
+using PixiEditor.Common;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph;
 
 
-public interface IReadOnlyBlackboard
+public interface IReadOnlyBlackboard : ICacheable
 {
 {
     public IReadOnlyVariable? GetVariable(string variableName);
     public IReadOnlyVariable? GetVariable(string variableName);
     public IReadOnlyDictionary<string, IReadOnlyVariable> Variables { get; }
     public IReadOnlyDictionary<string, IReadOnlyVariable> Variables { get; }

+ 1 - 72
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Brushes/BrushOutputNode.cs

@@ -49,9 +49,6 @@ public class BrushOutputNode : Node
 
 
     public InputProperty<IReadOnlyNodeGraph> Previous { get; }
     public InputProperty<IReadOnlyNodeGraph> Previous { get; }
 
 
-    public Texture PointPreviewTexture { get; private set; }
-    public Texture StrokePreviewTexture { get; private set; }
-
     internal Texture ContentTexture;
     internal Texture ContentTexture;
 
 
     private TextureCache cache = new();
     private TextureCache cache = new();
@@ -130,8 +127,6 @@ public class BrushOutputNode : Node
             RenderPreview(preview.Texture.DrawingSurface, adjusted);
             RenderPreview(preview.Texture.DrawingSurface, adjusted);
             preview.Texture.DrawingSurface.Canvas.RestoreToCount(saved);
             preview.Texture.DrawingSurface.Canvas.RestoreToCount(saved);
         }
         }
-
-        GeneratePreviews(ctx.Graph);
     }
     }
 
 
     private void RenderPreview(DrawingSurface surface, RenderContext context)
     private void RenderPreview(DrawingSurface surface, RenderContext context)
@@ -231,7 +226,7 @@ public class BrushOutputNode : Node
         }
         }
     }
     }
 
 
-    private void DrawPointPreview(ChunkyImage img, RenderContext context, int size, VecD pos)
+    public void DrawPointPreview(ChunkyImage img, RenderContext context, int size, VecD pos)
     {
     {
         previewEngine.ExecuteBrush(img,
         previewEngine.ExecuteBrush(img,
             new BrushData(context.Graph, Id) { StrokeWidth = size, AntiAliasing = true },
             new BrushData(context.Graph, Id) { StrokeWidth = size, AntiAliasing = true },
@@ -241,72 +236,6 @@ public class BrushOutputNode : Node
             new EditorData(Colors.White, Colors.Black));
             new EditorData(Colors.White, Colors.Black));
     }
     }
 
 
-    private void GeneratePreviews(IReadOnlyNodeGraph graph)
-    {
-        if (PointPreviewTexture == null || PointPreviewTexture.Size.X != PointPreviewSize ||
-            PointPreviewTexture.Size.Y != PointPreviewSize)
-        {
-            PointPreviewTexture?.Dispose();
-            PointPreviewTexture = Texture.ForDisplay(new VecI(PointPreviewSize, PointPreviewSize));
-        }
-
-        if (StrokePreviewTexture == null || StrokePreviewTexture.Size.X != StrokePreviewSizeX ||
-            StrokePreviewTexture.Size.Y != StrokePreviewSizeY)
-        {
-            StrokePreviewTexture?.Dispose();
-            StrokePreviewTexture = Texture.ForDisplay(new VecI(StrokePreviewSizeX, StrokePreviewSizeY));
-        }
-
-        PointPreviewTexture.DrawingSurface.Canvas.Clear();
-        StrokePreviewTexture.DrawingSurface.Canvas.Clear();
-
-        RenderContext context = new RenderContext(PointPreviewTexture.DrawingSurface.Canvas, 0, ChunkResolution.Full,
-            new VecI(PointPreviewSize, PointPreviewSize), new VecI(PointPreviewSize), ColorSpace.CreateSrgb(),
-            SamplingOptions.Default,
-           graph);
-
-        using var chunkyImage = new ChunkyImage(new VecI(PointPreviewSize, PointPreviewSize), context.ProcessingColorSpace);
-
-        DrawPointPreview(chunkyImage, context, PointPreviewSize, new VecD(PointPreviewSize / 2f, PointPreviewSize / 2f));
-        chunkyImage.CommitChanges();
-        chunkyImage.DrawCommittedChunkOn(
-            VecI.Zero, ChunkResolution.Full, PointPreviewTexture.DrawingSurface.Canvas, VecD.Zero);
-
-        context.RenderOutputSize = new VecI(StrokePreviewSizeX, StrokePreviewSizeY);
-        context.DocumentSize = new VecI(StrokePreviewSizeX, StrokePreviewSizeY);
-        context.RenderSurface = StrokePreviewTexture.DrawingSurface.Canvas;
-
-        using var strokeChunkyImage = new ChunkyImage(new VecI(StrokePreviewSizeX, StrokePreviewSizeY), context.ProcessingColorSpace);
-        DrawStrokePreview(strokeChunkyImage, context, StrokePreviewSizeY / 2, new VecD(0, YOffsetInPreview));
-        strokeChunkyImage.CommitChanges();
-        strokeChunkyImage.DrawCommittedChunkOn(
-            VecI.Zero, ChunkResolution.Full, StrokePreviewTexture.DrawingSurface.Canvas, VecD.Zero);
-    }
-
-    public override void SerializeAdditionalData(IReadOnlyDocument target, Dictionary<string, object> additionalData)
-    {
-        base.SerializeAdditionalData(target, additionalData);
-        GeneratePreviews(target.NodeGraph);
-        additionalData["PointPreviewTexture"] = PointPreviewTexture;
-        additionalData["StrokePreviewTexture"] = StrokePreviewTexture;
-    }
-
-    internal override void DeserializeAdditionalData(IReadOnlyDocument target, IReadOnlyDictionary<string, object> data,
-        List<IChangeInfo> infos)
-    {
-        base.DeserializeAdditionalData(target, data, infos);
-        if (data.TryGetValue("PointPreviewTexture", out var texObj) && texObj is Texture tex)
-        {
-            PointPreviewTexture = tex;
-        }
-
-        if (data.TryGetValue("StrokePreviewTexture", out var strokeTexObj) && strokeTexObj is Texture strokeTex)
-        {
-            StrokePreviewTexture = strokeTex;
-        }
-    }
-
-
     public override Node CreateCopy()
     public override Node CreateCopy()
     {
     {
         return new BrushOutputNode();
         return new BrushOutputNode();

+ 4 - 3
src/PixiEditor.UI.Common/Controls/DropDownButton.axaml

@@ -2,11 +2,12 @@
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
   <ControlTheme x:Key="{x:Type DropDownButton}"
   <ControlTheme x:Key="{x:Type DropDownButton}"
                 TargetType="DropDownButton">
                 TargetType="DropDownButton">
-    <Setter Property="Background" Value="{DynamicResource ThemeControlMidBrush}" />
-    <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderLowBrush}" />
+    <Setter Property="Background" Value="{DynamicResource ThemeControlLowBrush}" />
+    <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}" />
     <Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}" />
     <Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}" />
     <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
     <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
-    <Setter Property="HorizontalContentAlignment" Value="Center" />
+    <Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
+    <Setter Property="HorizontalContentAlignment" Value="Left" />
     <Setter Property="VerticalContentAlignment" Value="Center" />
     <Setter Property="VerticalContentAlignment" Value="Center" />
     <Setter Property="Padding" Value="4" />
     <Setter Property="Padding" Value="4" />
     <Setter Property="Template">
     <Setter Property="Template">

+ 6 - 26
src/PixiEditor/Models/BrushEngine/Brush.cs

@@ -1,8 +1,14 @@
 using Avalonia.Platform;
 using Avalonia.Platform;
+using ChunkyImageLib;
+using ChunkyImageLib.DataHolders;
 using Drawie.Backend.Core;
 using Drawie.Backend.Core;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
+using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Brushes;
 using PixiEditor.ChangeableDocument.Changeables.Brushes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Brushes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Brushes;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.IO;
 using PixiEditor.Models.IO;
 using PixiEditor.ViewModels.Document.Nodes.Brushes;
 using PixiEditor.ViewModels.Document.Nodes.Brushes;
@@ -16,8 +22,6 @@ internal class Brush : IBrush
     public string Name { get; set; }
     public string Name { get; set; }
     public string? FilePath { get; }
     public string? FilePath { get; }
     public Guid Id { get; }
     public Guid Id { get; }
-    public Texture StrokePreview { get; }
-    public Texture? PointPreview { get; }
 
 
     public Brush(Uri uri)
     public Brush(Uri uri)
     {
     {
@@ -52,9 +56,6 @@ internal class Brush : IBrush
 
 
         Name = name;
         Name = name;
         Document = doc;
         Document = doc;
-        var previews = ExtractPreviews(doc);
-        StrokePreview = previews.strokePreview;
-        PointPreview = previews.pointPreview;
 
 
         stream.Close();
         stream.Close();
         stream.Dispose();
         stream.Dispose();
@@ -66,9 +67,6 @@ internal class Brush : IBrush
         Document = brushDocument;
         Document = brushDocument;
         FilePath = brushDocument.FullFilePath;
         FilePath = brushDocument.FullFilePath;
         Id = brushDocument.NodeGraphHandler.AllNodes.OfType<BrushOutputNodeViewModel>().FirstOrDefault()?.Id ?? Guid.NewGuid();
         Id = brushDocument.NodeGraphHandler.AllNodes.OfType<BrushOutputNodeViewModel>().FirstOrDefault()?.Id ?? Guid.NewGuid();
-        var previews = ExtractPreviews(brushDocument);
-        StrokePreview = previews.strokePreview;
-        PointPreview = previews.pointPreview;
     }
     }
 
 
     public Brush(string name, IDocument brushDocument, Guid id)
     public Brush(string name, IDocument brushDocument, Guid id)
@@ -77,24 +75,6 @@ internal class Brush : IBrush
         Document = brushDocument;
         Document = brushDocument;
         FilePath = brushDocument.FullFilePath;
         FilePath = brushDocument.FullFilePath;
         Id = id;
         Id = id;
-        var previews = ExtractPreviews(brushDocument);
-        StrokePreview = previews.strokePreview;
-        PointPreview = previews.pointPreview;
-    }
-
-    private (Texture? strokePreview, Texture? pointPreview) ExtractPreviews(IDocument brushDocument)
-    {
-        using var graph = brushDocument.ShareGraph();
-        BrushOutputNode outputNode =
-            graph.TryAccessData().AllNodes.OfType<BrushOutputNode>().FirstOrDefault();
-        if (outputNode != null)
-        {
-            Texture? strokePreview = outputNode.StrokePreviewTexture;
-            Texture? pointPreview = outputNode.PointPreviewTexture;
-            return (strokePreview, pointPreview);
-        }
-
-        return (null, null);
     }
     }
 
 
     public override string ToString()
     public override string ToString()

+ 9 - 12
src/PixiEditor/Models/Controllers/BrushLibrary.cs

@@ -41,20 +41,17 @@ internal class BrushLibrary
                     stream.ReadExactly(buffer, 0, buffer.Length);
                     stream.ReadExactly(buffer, 0, buffer.Length);
                     var doc = Importer.ImportDocument(buffer, null);
                     var doc = Importer.ImportDocument(buffer, null);
 
 
-                    doc.Operations.InvokeCustomAction(() =>
+                    using var graph = doc.ShareGraph();
+                    BrushOutputNode outputNode =
+                        graph.TryAccessData().AllNodes.OfType<BrushOutputNode>().FirstOrDefault();
+                    string name = Path.GetFileNameWithoutExtension(localPath);
+                    if (outputNode != null)
                     {
                     {
-                        using var graph = doc.ShareGraph();
-                        BrushOutputNode outputNode =
-                            graph.TryAccessData().AllNodes.OfType<BrushOutputNode>().FirstOrDefault();
-                        string name = Path.GetFileNameWithoutExtension(localPath);
-                        if (outputNode != null)
-                        {
-                            name = outputNode.BrushName.Value;
-                        }
+                        name = outputNode.BrushName.Value;
+                    }
 
 
-                        var brush = new Brush(name, doc);
-                        brushes.Add(brush.Id, brush);
-                    }, false);
+                    var brush = new Brush(name, doc);
+                    brushes.Add(brush.Id, brush);
                 }
                 }
                 catch (Exception ex)
                 catch (Exception ex)
                 {
                 {

+ 152 - 0
src/PixiEditor/ViewModels/BrushSystem/BrushViewModel.cs

@@ -0,0 +1,152 @@
+using ChunkyImageLib;
+using ChunkyImageLib.DataHolders;
+using Drawie.Backend.Core;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.Surfaces.ImageData;
+using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Brushes;
+using PixiEditor.ChangeableDocument.Rendering;
+using PixiEditor.Models.BrushEngine;
+
+namespace PixiEditor.ViewModels.BrushSystem;
+
+internal class BrushViewModel : ViewModelBase
+{
+    private Texture pointPreviewTexture;
+    private Texture strokeTexture;
+    private Brush brush;
+
+    public Texture PointPreviewTexture
+    {
+        get
+        {
+            if (CacheChanged())
+            {
+                GeneratePreviewTextures();
+            }
+
+            return pointPreviewTexture;
+        }
+        set => SetProperty(ref pointPreviewTexture, value);
+    }
+
+    public Texture DrawingStrokeTexture
+    {
+        get
+        {
+            if (CacheChanged())
+            {
+                GeneratePreviewTextures();
+            }
+
+            return strokeTexture;
+        }
+        set => SetProperty(ref strokeTexture, value);
+    }
+
+    public string Name
+    {
+        get => Brush?.Name ?? "Unnamed Brush";
+    }
+
+    public Brush Brush
+    {
+        get { return brush; }
+        set
+        {
+            if (SetProperty(ref brush, value))
+            {
+                GeneratePreviewTextures();
+            }
+        }
+    }
+
+    private int lastTextureCache;
+
+    public BrushViewModel(Brush brush)
+    {
+        Brush = brush;
+        lastTextureCache = 0;
+    }
+
+    private void GeneratePreviewTextures()
+    {
+        BrushOutputNode? brushNode =
+            Brush?.Document?.AccessInternalReadOnlyDocument().NodeGraph.LookupNode(Brush.Id) as BrushOutputNode;
+        if (brushNode == null)
+            return;
+
+        pointPreviewTexture?.Dispose();
+        strokeTexture?.Dispose();
+
+        pointPreviewTexture =
+            Texture.ForDisplay(new VecI(BrushOutputNode.PointPreviewSize, BrushOutputNode.PointPreviewSize));
+        strokeTexture =
+            Texture.ForDisplay(new VecI(BrushOutputNode.StrokePreviewSizeX, BrushOutputNode.StrokePreviewSizeY));
+
+        var pointImage = new ChunkyImage(new VecI(BrushOutputNode.PointPreviewSize, BrushOutputNode.PointPreviewSize),
+            ColorSpace.CreateSrgb());
+        var strokeImage = new ChunkyImage(
+            new VecI(BrushOutputNode.StrokePreviewSizeX, BrushOutputNode.StrokePreviewSizeY),
+            ColorSpace.CreateSrgb());
+
+        var context = new RenderContext(
+            PointPreviewTexture.DrawingSurface.Canvas,
+            0,
+            ChunkResolution.Full,
+            PointPreviewTexture.Size,
+            PointPreviewTexture.Size,
+            ColorSpace.CreateSrgb(),
+            SamplingOptions.Bilinear,
+            Brush?.Document.AccessInternalReadOnlyDocument().NodeGraph);
+
+        brushNode.DrawPointPreview(pointImage, context,
+            BrushOutputNode.PointPreviewSize,
+            new VecD(BrushOutputNode.PointPreviewSize / 2,
+                BrushOutputNode.PointPreviewSize / 2));
+
+        pointImage.DrawMostUpToDateRegionOn(
+            new RectI(0, 0, pointImage.CommittedSize.X, pointImage.CommittedSize.Y),
+            ChunkResolution.Full,
+            PointPreviewTexture.DrawingSurface.Canvas,
+            VecI.Zero, null, SamplingOptions.Bilinear);
+
+        context.RenderOutputSize = strokeTexture.Size;
+        context.DocumentSize = strokeTexture.Size;
+        context.RenderSurface = strokeTexture.DrawingSurface.Canvas;
+
+        brushNode.DrawStrokePreview(strokeImage, context,
+            BrushOutputNode.StrokePreviewSizeY / 2,
+            new VecD(0, BrushOutputNode.YOffsetInPreview));
+
+        strokeImage.DrawMostUpToDateRegionOn(
+            new RectI(0, 0, strokeImage.CommittedSize.X, strokeImage.CommittedSize.Y),
+            ChunkResolution.Full,
+            strokeTexture.DrawingSurface.Canvas,
+            VecI.Zero, null, SamplingOptions.Bilinear);
+
+        OnPropertyChanged(nameof(DrawingStrokeTexture));
+        OnPropertyChanged(nameof(PointPreviewTexture));
+    }
+
+    private bool CacheChanged()
+    {
+        var doc = Brush?.Document.AccessInternalReadOnlyDocument();
+        if (doc == null)
+            return false;
+
+        int currentCache = doc.NodeGraph.GetCacheHash();
+        HashCode hash = new();
+        hash.Add(currentCache);
+        hash.Add(doc.Blackboard.GetCacheHash());
+
+        currentCache = hash.ToHashCode();
+        if (currentCache != lastTextureCache)
+        {
+            lastTextureCache = currentCache;
+            return true;
+        }
+
+        return false;
+    }
+}

+ 14 - 3
src/PixiEditor/ViewModels/Tools/ToolSettings/Settings/BrushSettingViewModel.cs

@@ -1,11 +1,12 @@
 using System.Collections.ObjectModel;
 using System.Collections.ObjectModel;
 using PixiEditor.Models.BrushEngine;
 using PixiEditor.Models.BrushEngine;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Controllers;
+using PixiEditor.ViewModels.BrushSystem;
 using PixiEditor.ViewModels.SubViewModels;
 using PixiEditor.ViewModels.SubViewModels;
 
 
 namespace PixiEditor.ViewModels.Tools.ToolSettings.Settings;
 namespace PixiEditor.ViewModels.Tools.ToolSettings.Settings;
 
 
-internal class BrushSettingViewModel : Setting<Brush>
+internal class BrushSettingViewModel : Setting<BrushViewModel>
 {
 {
     private static BrushLibrary library;
     private static BrushLibrary library;
 
 
@@ -22,11 +23,21 @@ internal class BrushSettingViewModel : Setting<Brush>
         }
         }
     }
     }
 
 
-    public ObservableCollection<Brush> AllBrushes => new ObservableCollection<Brush>(Library.Brushes.Values);
+    public ObservableCollection<BrushViewModel> AllBrushes => viewModels;
+
+    private ObservableCollection<BrushViewModel> viewModels = new ObservableCollection<BrushViewModel>();
+
     public BrushSettingViewModel(string name, string label) : base(name)
     public BrushSettingViewModel(string name, string label) : base(name)
     {
     {
         Label = label;
         Label = label;
-        Library.BrushesChanged += () => OnPropertyChanged(nameof(AllBrushes));
+        Library.BrushesChanged += () =>
+        {
+            viewModels = new ObservableCollection<BrushViewModel>(Library.Brushes.Values.Select(b => new BrushViewModel(b)));
+            OnPropertyChanged(nameof(AllBrushes));
+        };
+
+        viewModels = new ObservableCollection<BrushViewModel>(Library.Brushes.Values.Select(b => new BrushViewModel(b)));
+        OnPropertyChanged(nameof(AllBrushes));
     }
     }
 
 
     protected override object AdjustValue(object value)
     protected override object AdjustValue(object value)

+ 3 - 2
src/PixiEditor/ViewModels/Tools/ToolSettings/Toolbars/BrushToolbar.cs

@@ -3,6 +3,7 @@ using Drawie.Backend.Core.Surfaces.PaintImpl;
 using PixiEditor.ChangeableDocument.Changeables.Brushes;
 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.BrushSystem;
 using PixiEditor.ViewModels.Tools.ToolSettings.Settings;
 using PixiEditor.ViewModels.Tools.ToolSettings.Settings;
 using PixiEditor.Views.Overlays.BrushShapeOverlay;
 using PixiEditor.Views.Overlays.BrushShapeOverlay;
 
 
@@ -24,8 +25,8 @@ internal class BrushToolbar : Toolbar, IBrushToolbar
 
 
     public Brush Brush
     public Brush Brush
     {
     {
-        get => GetSetting<BrushSettingViewModel>(nameof(Brush)).Value;
-        set => GetSetting<BrushSettingViewModel>(nameof(Brush)).Value = value;
+        get => GetSetting<BrushSettingViewModel>(nameof(Brush)).Value?.Brush;
+        set => GetSetting<BrushSettingViewModel>(nameof(Brush)).Value = value != null ? new BrushViewModel(value) : null;
     }
     }
 
 
     public double Stabilization
     public double Stabilization

+ 1 - 1
src/PixiEditor/Views/Input/BrushItem.axaml

@@ -24,7 +24,7 @@
             <controls:DrawieTextureControl Height="30" Width="30"
             <controls:DrawieTextureControl Height="30" Width="30"
                                            SamplingOptions="Bilinear"
                                            SamplingOptions="Bilinear"
                                            Margin="0, 0, 5, 0"
                                            Margin="0, 0, 5, 0"
-                                           Texture="{Binding Brush.PointPreview}" DockPanel.Dock="Left" />
+                                           Texture="{Binding $parent[input:BrushItem].Brush.PointPreviewTexture}" DockPanel.Dock="Left" />
             <TextBlock VerticalAlignment="Center"
             <TextBlock VerticalAlignment="Center"
                        Width="70"
                        Width="70"
                        ToolTip.Tip="{Binding Brush.Name}"
                        ToolTip.Tip="{Binding Brush.Name}"

+ 10 - 8
src/PixiEditor/Views/Input/BrushItem.axaml.cs

@@ -16,12 +16,13 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Brushes;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.ChangeableDocument.Rendering.ContextData;
 using PixiEditor.ChangeableDocument.Rendering.ContextData;
 using PixiEditor.Models.BrushEngine;
 using PixiEditor.Models.BrushEngine;
+using PixiEditor.ViewModels.BrushSystem;
 
 
 namespace PixiEditor.Views.Input;
 namespace PixiEditor.Views.Input;
 
 
 internal partial class BrushItem : UserControl
 internal partial class BrushItem : UserControl
 {
 {
-    public static readonly StyledProperty<Brush> BrushProperty = AvaloniaProperty.Register<BrushItem, Brush>("Brush");
+    public static readonly StyledProperty<BrushViewModel> BrushProperty = AvaloniaProperty.Register<BrushItem, BrushViewModel>("Brush");
 
 
     public static readonly StyledProperty<Texture> DrawingStrokeTextureProperty =
     public static readonly StyledProperty<Texture> DrawingStrokeTextureProperty =
         AvaloniaProperty.Register<BrushItem, Texture>(
         AvaloniaProperty.Register<BrushItem, Texture>(
@@ -33,9 +34,9 @@ internal partial class BrushItem : UserControl
         set => SetValue(DrawingStrokeTextureProperty, value);
         set => SetValue(DrawingStrokeTextureProperty, value);
     }
     }
 
 
-    public Brush Brush
+    public BrushViewModel Brush
     {
     {
-        get { return (Brush)GetValue(BrushProperty); }
+        get { return (BrushViewModel)GetValue(BrushProperty); }
         set { SetValue(BrushProperty, value); }
         set { SetValue(BrushProperty, value); }
     }
     }
 
 
@@ -52,7 +53,8 @@ internal partial class BrushItem : UserControl
         BrushProperty.Changed.AddClassHandler<BrushItem>((x, e) =>
         BrushProperty.Changed.AddClassHandler<BrushItem>((x, e) =>
         {
         {
             x.StopStrokePreviewLoop();
             x.StopStrokePreviewLoop();
-            x.DrawingStrokeTexture = x.Brush?.StrokePreview;
+            var brush = e.NewValue as BrushViewModel;
+            x.DrawingStrokeTexture = brush?.DrawingStrokeTexture;
         });
         });
     }
     }
 
 
@@ -78,7 +80,7 @@ internal partial class BrushItem : UserControl
             return;
             return;
 
 
         BrushOutputNode? brushNode =
         BrushOutputNode? brushNode =
-            Brush.Document?.AccessInternalReadOnlyDocument().NodeGraph.LookupNode(Brush.Id) as BrushOutputNode;
+            Brush?.Brush?.Document?.AccessInternalReadOnlyDocument().NodeGraph.LookupNode(Brush?.Brush?.Id ?? Guid.Empty) as BrushOutputNode;
         if (brushNode == null)
         if (brushNode == null)
             return;
             return;
 
 
@@ -116,7 +118,7 @@ internal partial class BrushItem : UserControl
             if (!enumerator.MoveNext())
             if (!enumerator.MoveNext())
             {
             {
                 isPreviewingStroke = false;
                 isPreviewingStroke = false;
-                DrawingStrokeTexture = Brush?.StrokePreview;
+                DrawingStrokeTexture = Brush?.DrawingStrokeTexture;
                 return false;
                 return false;
             }
             }
 
 
@@ -142,7 +144,7 @@ internal partial class BrushItem : UserControl
             previewTexture.Size,
             previewTexture.Size,
             ColorSpace.CreateSrgb(),
             ColorSpace.CreateSrgb(),
             SamplingOptions.Bilinear,
             SamplingOptions.Bilinear,
-            Brush.Document.AccessInternalReadOnlyDocument().NodeGraph);
+            Brush?.Brush?.Document.AccessInternalReadOnlyDocument().NodeGraph);
     }
     }
 
 
     private void StopStrokePreviewLoop()
     private void StopStrokePreviewLoop()
@@ -154,6 +156,6 @@ internal partial class BrushItem : UserControl
             disposable.Dispose();
             disposable.Dispose();
         }
         }
 
 
-        DrawingStrokeTexture = Brush?.StrokePreview;
+        DrawingStrokeTexture = Brush?.DrawingStrokeTexture;
     }
     }
 }
 }

+ 4 - 4
src/PixiEditor/Views/Input/BrushPicker.axaml

@@ -13,10 +13,10 @@
              x:Class="PixiEditor.Views.Input.BrushPicker">
              x:Class="PixiEditor.Views.Input.BrushPicker">
     <Panel>
     <Panel>
         <controls:DrawieTextureControl
         <controls:DrawieTextureControl
-            Width="36" Height="16"
+            Width="36" Height="21"
             ZIndex="1"
             ZIndex="1"
             SamplingOptions="Bilinear"
             SamplingOptions="Bilinear"
-            Texture="{Binding Path=SelectedBrush.PointPreview, RelativeSource={RelativeSource FindAncestor, AncestorType=input:BrushPicker}}" />
+            Texture="{Binding Path=SelectedBrush.PointPreviewTexture, RelativeSource={RelativeSource FindAncestor, AncestorType=input:BrushPicker}}" />
         <Panel.Styles>
         <Panel.Styles>
             <Style Selector="Panel > ToggleButton:checked">
             <Style Selector="Panel > ToggleButton:checked">
                 <Setter Property="Background" Value="{DynamicResource ThemeControlMidBrush}" />
                 <Setter Property="Background" Value="{DynamicResource ThemeControlMidBrush}" />
@@ -25,7 +25,7 @@
                 <Setter Property="Background" Value="{DynamicResource ThemeControlMidBrush}" />
                 <Setter Property="Background" Value="{DynamicResource ThemeControlMidBrush}" />
             </Style>
             </Style>
         </Panel.Styles>
         </Panel.Styles>
-        <ToggleButton Name="PopupToggle" Width="40" Height="20">
+        <ToggleButton Name="PopupToggle" Background="{DynamicResource ThemeBackgroundBrush}" Width="40" Height="25">
             <ToggleButton.Styles>
             <ToggleButton.Styles>
                 <Style Selector="FlyoutPresenter">
                 <Style Selector="FlyoutPresenter">
                     <Setter Property="Cursor" Value="Arrow" />
                     <Setter Property="Cursor" Value="Arrow" />
@@ -216,7 +216,7 @@
                                                 <visuals:TextureControl
                                                 <visuals:TextureControl
                                                     Width="50" Height="50"
                                                     Width="50" Height="50"
                                                     SamplingOptions="Bilinear"
                                                     SamplingOptions="Bilinear"
-                                                    Texture="{Binding PointPreview}" />
+                                                    Texture="{Binding PointPreviewTexture}" />
                                                 <Interaction.Behaviors>
                                                 <Interaction.Behaviors>
                                                     <ExecuteCommandOnPointerPressedBehavior
                                                     <ExecuteCommandOnPointerPressedBehavior
                                                         Command="{Binding $parent[input:BrushPicker].SelectBrushCommand}"
                                                         Command="{Binding $parent[input:BrushPicker].SelectBrushCommand}"

+ 15 - 14
src/PixiEditor/Views/Input/BrushPicker.axaml.cs

@@ -12,18 +12,19 @@ using Avalonia.VisualTree;
 using CommunityToolkit.Mvvm.Input;
 using CommunityToolkit.Mvvm.Input;
 using PixiEditor.Models.Palettes;
 using PixiEditor.Models.Palettes;
 using PixiEditor.UI.Common.Localization;
 using PixiEditor.UI.Common.Localization;
+using PixiEditor.ViewModels.BrushSystem;
 using Brush = PixiEditor.Models.BrushEngine.Brush;
 using Brush = PixiEditor.Models.BrushEngine.Brush;
 
 
 namespace PixiEditor.Views.Input;
 namespace PixiEditor.Views.Input;
 
 
 internal partial class BrushPicker : UserControl
 internal partial class BrushPicker : UserControl
 {
 {
-    public static readonly StyledProperty<ObservableCollection<Brush>> BrushesProperty =
-        AvaloniaProperty.Register<BrushPicker, ObservableCollection<Brush>>(
+    public static readonly StyledProperty<ObservableCollection<BrushViewModel>> BrushesProperty =
+        AvaloniaProperty.Register<BrushPicker, ObservableCollection<BrushViewModel>>(
             nameof(Brushes));
             nameof(Brushes));
 
 
-    public static readonly StyledProperty<ObservableCollection<Brush>> FilteredBrushesProperty =
-        AvaloniaProperty.Register<BrushPicker, ObservableCollection<Brush>>(
+    public static readonly StyledProperty<ObservableCollection<BrushViewModel>> FilteredBrushesProperty =
+        AvaloniaProperty.Register<BrushPicker, ObservableCollection<BrushViewModel>>(
             nameof(FilteredBrushes));
             nameof(FilteredBrushes));
 
 
     public static readonly StyledProperty<string> SearchTextProperty = AvaloniaProperty.Register<BrushPicker, string>(
     public static readonly StyledProperty<string> SearchTextProperty = AvaloniaProperty.Register<BrushPicker, string>(
@@ -75,23 +76,23 @@ internal partial class BrushPicker : UserControl
         set => SetValue(SearchTextProperty, value);
         set => SetValue(SearchTextProperty, value);
     }
     }
 
 
-    public ObservableCollection<Brush> FilteredBrushes
+    public ObservableCollection<BrushViewModel> FilteredBrushes
     {
     {
         get => GetValue(FilteredBrushesProperty);
         get => GetValue(FilteredBrushesProperty);
         set => SetValue(FilteredBrushesProperty, value);
         set => SetValue(FilteredBrushesProperty, value);
     }
     }
 
 
-    public ObservableCollection<Brush> Brushes
+    public ObservableCollection<BrushViewModel> Brushes
     {
     {
         get => GetValue(BrushesProperty);
         get => GetValue(BrushesProperty);
         set => SetValue(BrushesProperty, value);
         set => SetValue(BrushesProperty, value);
     }
     }
 
 
-    public static readonly StyledProperty<Brush?> SelectedBrushProperty =
-        AvaloniaProperty.Register<BrushPicker, Brush?>(
+    public static readonly StyledProperty<BrushViewModel?> SelectedBrushProperty =
+        AvaloniaProperty.Register<BrushPicker, BrushViewModel?>(
             nameof(SelectedBrush));
             nameof(SelectedBrush));
 
 
-    public Brush? SelectedBrush
+    public BrushViewModel? SelectedBrush
     {
     {
         get => GetValue(SelectedBrushProperty);
         get => GetValue(SelectedBrushProperty);
         set => SetValue(SelectedBrushProperty, value);
         set => SetValue(SelectedBrushProperty, value);
@@ -183,10 +184,10 @@ internal partial class BrushPicker : UserControl
 
 
     private void UpdateResults()
     private void UpdateResults()
     {
     {
-        var filtered = new ObservableCollection<Brush>();
+        var filtered = new ObservableCollection<BrushViewModel>();
         if (string.IsNullOrWhiteSpace(SearchText))
         if (string.IsNullOrWhiteSpace(SearchText))
         {
         {
-            filtered = new ObservableCollection<Brush>(Brushes);
+            filtered = new ObservableCollection<BrushViewModel>(Brushes);
         }
         }
         else
         else
         {
         {
@@ -202,7 +203,7 @@ internal partial class BrushPicker : UserControl
 
 
         filtered = SelectedSortingIndex switch
         filtered = SelectedSortingIndex switch
         {
         {
-            (int)BrushSorting.Alphabetical => new ObservableCollection<Brush>(filtered.OrderBy(b => b.Name)),
+            (int)BrushSorting.Alphabetical => new ObservableCollection<BrushViewModel>(filtered.OrderBy(b => b.Name)),
             _ => filtered
             _ => filtered
         };
         };
 
 
@@ -210,14 +211,14 @@ internal partial class BrushPicker : UserControl
         bool descending = SortingDirection == "descending";
         bool descending = SortingDirection == "descending";
         if (descending)
         if (descending)
         {
         {
-            filtered = new ObservableCollection<Brush>(filtered.Reverse());
+            filtered = new ObservableCollection<BrushViewModel>(filtered.Reverse());
         }
         }
 
 
         FilteredBrushes = filtered;
         FilteredBrushes = filtered;
     }
     }
 
 
     [RelayCommand]
     [RelayCommand]
-    public void SelectBrush(Brush brush)
+    public void SelectBrush(BrushViewModel brush)
     {
     {
         SelectedBrush = brush;
         SelectedBrush = brush;
         PopupToggle.IsChecked = false;
         PopupToggle.IsChecked = false;