Browse Source

Fill serialization and groups

flabbet 8 months ago
parent
commit
8e5b45d36e

+ 6 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/RectangleVectorData.cs

@@ -34,6 +34,12 @@ public class RectangleVectorData : ShapeVectorData, IReadOnlyRectangleData
         Center = center;
         Size = size;
     }
+    
+    public RectangleVectorData(double x, double y, double width, double height)
+    {
+        Center = new VecD(x + width / 2, y + height / 2);
+        Size = new VecD(width, height);
+    }
 
     public override void RasterizeGeometry(DrawingSurface drawingSurface)
     {

+ 1 - 1
src/PixiEditor/Helpers/DocumentViewModelBuilder.cs

@@ -373,7 +373,7 @@ internal class NodeGraphBuilder
 
     public NodeBuilder WithNodeOfType<T>(out int id) where T : IReadOnlyNode
     {
-        NodeBuilder builder = this.WithNodeOfType(typeof(VectorLayerNode))
+        NodeBuilder builder = this.WithNodeOfType(typeof(T))
             .WithId(AllNodes.Count);
 
         id = AllNodes.Count;

+ 18 - 3
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/TransformSelectedExecutor.cs

@@ -73,9 +73,24 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
             memberCorners.Add(member.Id, targetCorners);
         }
 
-        ShapeCorners masterCorners = memberCorners.Count == 1
-            ? memberCorners.FirstOrDefault().Value
-            : new ShapeCorners(memberCorners.Values.Select(static c => c.AABBBounds).Aggregate((a, b) => a.Union(b)));
+        ShapeCorners masterCorners;
+        if (memberCorners.Count == 1)
+        {
+            masterCorners = memberCorners.FirstOrDefault().Value;
+        }
+        else
+        {
+            var aabbBounds = memberCorners.Values.Select(static c => c.AABBBounds);
+            var bounds = aabbBounds as RectD[] ?? aabbBounds.ToArray();
+            if (bounds.Length != 0)
+            {
+                masterCorners = new ShapeCorners(bounds.Aggregate((a, b) => a.Union(b)));
+            }
+            else
+            {
+                return ExecutionState.Error;
+            }
+        }
 
         if (masterCorners.AABBBounds.Width == 0 || masterCorners.AABBBounds.Height == 0)
         {

+ 104 - 49
src/PixiEditor/Models/IO/CustomDocumentFormats/SvgDocumentBuilder.cs

@@ -1,4 +1,5 @@
-using Drawie.Backend.Core.Vector;
+using System.Diagnostics.CodeAnalysis;
+using Drawie.Backend.Core.Vector;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
@@ -28,53 +29,13 @@ internal class SvgDocumentBuilder : IDocumentBuilder
                 int? lastId = null;
                 foreach (SvgElement element in document.Children)
                 {
-                    LocalizedString name = "";
                     if (element is SvgPrimitive primitive)
                     {
-                        ShapeVectorData shapeData = null;
-                        if (element is SvgEllipse or SvgCircle)
-                        {
-                            shapeData = AddEllipse(element);
-                            name = VectorEllipseToolViewModel.NewLayerKey;
-                        }
-                        else if (element is SvgLine line)
-                        {
-                            shapeData = AddLine(line);
-                            name = VectorLineToolViewModel.NewLayerKey;
-                        }
-                        else if (element is SvgPath pathElement)
-                        {
-                            shapeData = AddPath(pathElement);
-                            name = VectorPathToolViewModel.NewLayerKey;
-                        }
-                        /*else if (element is SvgRectangle rect)
-                        {
-                            AddRect(graph, rect);
-                        }
-                        else if (element is SvgGroup group)
-                        {
-                            AddGroup(graph, group);
-                        }*/
-
-                        AddCommonShapeData(primitive, shapeData);
-
-                        NodeGraphBuilder.NodeBuilder nBuilder = graph.WithNodeOfType<VectorLayerNode>(out int id)
-                            .WithName(name)
-                            .WithAdditionalData(new Dictionary<string, object>() { { "ShapeData", shapeData } });
-
-                        if (lastId != null)
-                        {
-                            nBuilder.WithConnections([
-                                new PropertyConnection()
-                                {
-                                    InputPropertyName = "Background",
-                                    OutputPropertyName = "Output",
-                                    OutputNodeId = lastId.Value
-                                }
-                            ]);
-                        }
-
-                        lastId = id;
+                        lastId = AddPrimitive(element, primitive, graph, lastId);
+                    }
+                    else if (element is SvgGroup group)
+                    {
+                        lastId = AddGroup(group, graph, lastId);
                     }
                 }
 
@@ -82,6 +43,94 @@ internal class SvgDocumentBuilder : IDocumentBuilder
             });
     }
 
+    [return: NotNull]
+    private int? AddPrimitive(SvgElement element, SvgPrimitive primitive, NodeGraphBuilder graph,
+        int? lastId, string connectionName = "Background")
+    {
+        LocalizedString name = "";
+        ShapeVectorData shapeData = null;
+        if (element is SvgEllipse or SvgCircle)
+        {
+            shapeData = AddEllipse(element);
+            name = VectorEllipseToolViewModel.NewLayerKey;
+        }
+        else if (element is SvgLine line)
+        {
+            shapeData = AddLine(line);
+            name = VectorLineToolViewModel.NewLayerKey;
+        }
+        else if (element is SvgPath pathElement)
+        {
+            shapeData = AddPath(pathElement);
+            name = VectorPathToolViewModel.NewLayerKey;
+        }
+        else if (element is SvgRectangle rect)
+        {
+            shapeData = AddRect(rect);
+            name = VectorRectangleToolViewModel.NewLayerKey;
+        }
+
+        AddCommonShapeData(primitive, shapeData);
+
+        NodeGraphBuilder.NodeBuilder nBuilder = graph.WithNodeOfType<VectorLayerNode>(out int id)
+            .WithName(name)
+            .WithAdditionalData(new Dictionary<string, object>() { { "ShapeData", shapeData } });
+
+        if (lastId != null)
+        {
+            nBuilder.WithConnections([
+                new PropertyConnection()
+                {
+                    InputPropertyName = connectionName, OutputPropertyName = "Output", OutputNodeId = lastId.Value
+                }
+            ]);
+        }
+
+        lastId = id;
+        return lastId;
+    }
+
+    private int? AddGroup(SvgGroup group, NodeGraphBuilder graph, int? lastId, string connectionName = "Background")
+    {
+        string connectTo = FolderNode.ContentInternalName;
+
+        int? childId = null;
+        connectTo = "Background";
+        
+        foreach (var child in group.Children)
+        {
+            if (child is SvgPrimitive primitive)
+            {
+                childId = AddPrimitive(child, primitive, graph, childId, connectTo);
+            }
+            else if (child is SvgGroup childGroup)
+            {
+                childId = AddGroup(childGroup, graph, childId, connectTo);
+            }
+        }
+
+        NodeGraphBuilder.NodeBuilder nBuilder = graph.WithNodeOfType<FolderNode>(out int id)
+            .WithName(group.Id.Unit != null ? group.Id.Unit.Value.Value : new LocalizedString("NEW_FOLDER"));
+
+        if (lastId != null)
+        {
+            nBuilder.WithConnections([
+                new PropertyConnection()
+                {
+                    InputPropertyName = connectionName, OutputPropertyName = "Output", OutputNodeId = lastId.Value
+                },
+                new PropertyConnection()
+                {
+                    InputPropertyName = "Content", OutputPropertyName = "Output", OutputNodeId = childId.Value
+                }
+            ]);
+        }
+
+        lastId = id;
+
+        return lastId;
+    }
+
     private EllipseVectorData AddEllipse(SvgElement element)
     {
         if (element is SvgCircle circle)
@@ -111,8 +160,8 @@ internal class SvgDocumentBuilder : IDocumentBuilder
     private PathVectorData AddPath(SvgPath element)
     {
         VectorPath path = VectorPath.FromSvgPath(element.PathData.Unit.Value.Value);
-        
-        if(element.FillRule.Unit != null)
+
+        if (element.FillRule.Unit != null)
         {
             path.FillType = element.FillRule.Unit.Value.Value switch
             {
@@ -121,10 +170,16 @@ internal class SvgDocumentBuilder : IDocumentBuilder
                 _ => PathFillType.Winding
             };
         }
-        
+
         return new PathVectorData(path);
     }
 
+    private RectangleVectorData AddRect(SvgRectangle element)
+    {
+        return new RectangleVectorData(element.X.Unit.Value.Value, element.Y.Unit.Value.Value,
+            element.Width.Unit.Value.Value, element.Height.Unit.Value.Value);
+    }
+
     private void AddCommonShapeData(SvgPrimitive primitive, ShapeVectorData? shapeData)
     {
         if (shapeData == null)

+ 5 - 0
src/PixiEditor/Models/Serialization/Factories/ByteBuilder.cs

@@ -80,4 +80,9 @@ public class ByteBuilder
     {
         _data.AddRange(BitConverter.GetBytes(value));
     }
+
+    public void AddBool(bool value)
+    {
+        _data.Add(value ? (byte)1 : (byte)0);
+    }
 }

+ 9 - 0
src/PixiEditor/Models/Serialization/Factories/ByteExtractor.cs

@@ -104,4 +104,13 @@ public class ByteExtractor
         
         return value;
     }
+
+    public bool GetBool()
+    {
+        bool value = BitConverter.ToBoolean(_data, Position);
+        
+        Position += sizeof(bool);
+        
+        return value;
+    }
 }

+ 2 - 0
src/PixiEditor/Models/Serialization/Factories/EllipseSerializationFactory.cs

@@ -16,6 +16,7 @@ public class EllipseSerializationFactory : VectorShapeSerializationFactory<Ellip
     }
 
     protected override bool DeserializeVectorData(ByteExtractor extractor, Matrix3X3 matrix, Color strokeColor,
+        bool fill,
         Color fillColor,
         float strokeWidth, (string serializerName, string serializerVersion) serializerData,
         out EllipseVectorData original)
@@ -26,6 +27,7 @@ public class EllipseSerializationFactory : VectorShapeSerializationFactory<Ellip
         original = new EllipseVectorData(center, radius)
         {
             StrokeColor = strokeColor,
+            Fill = fill,
             FillColor = fillColor,
             StrokeWidth = strokeWidth,
             TransformationMatrix = matrix

+ 1 - 0
src/PixiEditor/Models/Serialization/Factories/LineSerializationFactory.cs

@@ -16,6 +16,7 @@ internal class LineSerializationFactory : VectorShapeSerializationFactory<LineVe
     }
 
     protected override bool DeserializeVectorData(ByteExtractor extractor, Matrix3X3 matrix, Color strokeColor,
+        bool fill,
         Color fillColor,
         float strokeWidth, (string serializerName, string serializerVersion) serializerData,
         out LineVectorData original)

+ 3 - 1
src/PixiEditor/Models/Serialization/Factories/PointsDataSerializationFactory.cs

@@ -14,6 +14,7 @@ internal class PointsDataSerializationFactory : VectorShapeSerializationFactory<
     }
 
     protected override bool DeserializeVectorData(ByteExtractor extractor, Matrix3X3 matrix, Color strokeColor,
+        bool fill,
         Color fillColor,
         float strokeWidth, (string serializerName, string serializerVersion) serializerData,
         out PointsVectorData original)
@@ -24,7 +25,8 @@ internal class PointsDataSerializationFactory : VectorShapeSerializationFactory<
             StrokeColor = strokeColor,
             FillColor = fillColor,
             StrokeWidth = strokeWidth,
-            TransformationMatrix = matrix
+            TransformationMatrix = matrix,
+            Fill = fill
         };
 
         return true;

+ 3 - 1
src/PixiEditor/Models/Serialization/Factories/RectangleSerializationFactory.cs

@@ -17,6 +17,7 @@ internal class RectangleSerializationFactory : VectorShapeSerializationFactory<R
     }
 
     protected override bool DeserializeVectorData(ByteExtractor extractor, Matrix3X3 matrix, Color strokeColor,
+        bool fill,
         Color fillColor,
         float strokeWidth, (string serializerName, string serializerVersion) serializerData,
         out RectangleVectorData original)
@@ -29,7 +30,8 @@ internal class RectangleSerializationFactory : VectorShapeSerializationFactory<R
             StrokeColor = strokeColor,
             FillColor = fillColor,
             StrokeWidth = strokeWidth,
-            TransformationMatrix = matrix
+            TransformationMatrix = matrix,
+            Fill = fill
         };
 
         return true;

+ 3 - 1
src/PixiEditor/Models/Serialization/Factories/VectorPathSerializationFactory.cs

@@ -30,6 +30,7 @@ internal class VectorPathSerializationFactory : VectorShapeSerializationFactory<
     }
 
     protected override bool DeserializeVectorData(ByteExtractor extractor, Matrix3X3 matrix, Color strokeColor,
+        bool fill,
         Color fillColor,
         float strokeWidth, (string serializerName, string serializerVersion) serializerData,
         out PathVectorData original)
@@ -50,7 +51,8 @@ internal class VectorPathSerializationFactory : VectorShapeSerializationFactory<
             StrokeColor = strokeColor,
             FillColor = fillColor,
             StrokeWidth = strokeWidth,
-            TransformationMatrix = matrix
+            TransformationMatrix = matrix,
+            Fill = fill
         };
 
         return true;

+ 18 - 1
src/PixiEditor/Models/Serialization/Factories/VectorShapeSerializationFactory.cs

@@ -11,6 +11,7 @@ public abstract class VectorShapeSerializationFactory<T> : SerializationFactory<
         ByteBuilder builder = new ByteBuilder();
         builder.AddMatrix3X3(original.TransformationMatrix);
         builder.AddColor(original.StrokeColor);
+        builder.AddBool(original.Fill);
         builder.AddColor(original.FillColor);
         builder.AddFloat(original.StrokeWidth);
         
@@ -34,6 +35,7 @@ public abstract class VectorShapeSerializationFactory<T> : SerializationFactory<
         
         Matrix3X3 matrix = extractor.GetMatrix3X3();
         Color strokeColor = extractor.GetColor();
+        bool fill = TryGetBool(extractor, serializerData);
         Color fillColor = extractor.GetColor();
         float strokeWidth;
         // Previous versions of the serializer saved stroke as int, and serializer data didn't exist
@@ -46,10 +48,25 @@ public abstract class VectorShapeSerializationFactory<T> : SerializationFactory<
             strokeWidth = extractor.GetFloat();
         }
 
-        return DeserializeVectorData(extractor, matrix, strokeColor, fillColor, strokeWidth, serializerData, out original);
+        return DeserializeVectorData(extractor, matrix, strokeColor, fill, fillColor, strokeWidth, serializerData, out original);
     }
     
     protected abstract bool DeserializeVectorData(ByteExtractor extractor, Matrix3X3 matrix, Color strokeColor,
+        bool fill,
         Color fillColor, float strokeWidth, (string serializerName, string serializerVersion) serializerData,
         out T original);
+
+    private bool TryGetBool(ByteExtractor extractor, (string serializerName, string serializerVersion) serializerData)
+    {
+        // Previous versions didn't have fill bool
+        if (serializerData.serializerName == "PixiEditor")
+        {
+            if(Version.TryParse(serializerData.serializerVersion, out Version version) && version is { Major: 2, Minor: 0, Build: 0, Revision: < 35 })
+            {
+                return true;
+            }
+        }
+
+        return extractor.GetBool();
+    }
 }

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

@@ -15,6 +15,8 @@ namespace PixiEditor.ViewModels.Tools.Tools;
 [Command.Tool(Key = Key.R)]
 internal class VectorRectangleToolViewModel : ShapeTool, IVectorRectangleToolHandler
 {
+    public const string NewLayerKey = "NEW_RECTANGLE_LAYER_NAME";
+    
     private string defaultActionDisplay = "RECTANGLE_TOOL_ACTION_DISPLAY_DEFAULT";
     public override string ToolNameLocalizationKey => "RECTANGLE_TOOL";
     public override bool IsErasable => false;
@@ -30,7 +32,7 @@ internal class VectorRectangleToolViewModel : ShapeTool, IVectorRectangleToolHan
     public override string DefaultIcon => PixiPerfectIcons.Square;
 
     public override Type LayerTypeToCreateOnEmptyUse { get; } = typeof(VectorLayerNode);
-    public string? DefaultNewLayerName { get; } = new LocalizedString("NEW_RECTANGLE_LAYER_NAME");
+    public string? DefaultNewLayerName { get; } = new LocalizedString(NewLayerKey);
 
     public override void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
     {