Browse Source

Merge pull request #972 from PixiEditor/fixes/18.06.2025

Fixes/18.06.2025
Krzysztof Krysiński 1 month ago
parent
commit
bd15643aae

+ 3 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/RectangleVectorData.cs

@@ -122,7 +122,9 @@ public class RectangleVectorData : ShapeVectorData, IReadOnlyRectangleData
         }
         else
         {
-            path.AddRoundRect(RectD.FromCenterAndSize(Center, Size), new VecD(CornerRadius));
+            double maxRadiusPx = Math.Min(Size.X, Size.Y) / 2f;
+            double radiusPx = CornerRadius * maxRadiusPx;
+            path.AddRoundRect(RectD.FromCenterAndSize(Center, Size), new VecD(radiusPx));
         }
 
         if (transformed)

+ 3 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/EllipseNode.cs

@@ -13,7 +13,7 @@ public class EllipseNode : ShapeNode<EllipseVectorData>
     public InputProperty<VecD> Radius { get; }
     public InputProperty<Paintable> StrokeColor { get; }
     public InputProperty<Paintable> FillColor { get; }
-    public InputProperty<int> StrokeWidth { get; }
+    public InputProperty<double> StrokeWidth { get; }
 
     public EllipseNode()
     {
@@ -22,13 +22,13 @@ public class EllipseNode : ShapeNode<EllipseVectorData>
             v => v.Min(new VecD(0)));
         StrokeColor = CreateInput<Paintable>("StrokeColor", "STROKE_COLOR", new Color(0, 0, 0, 255));
         FillColor = CreateInput<Paintable>("FillColor", "FILL_COLOR", new Color(0, 0, 0, 255));
-        StrokeWidth = CreateInput<int>("StrokeWidth", "STROKE_WIDTH", 1);
+        StrokeWidth = CreateInput<double>("StrokeWidth", "STROKE_WIDTH", 1);
     }
 
     protected override EllipseVectorData? GetShapeData(RenderContext context)
     {
         return new EllipseVectorData(Center.Value, Radius.Value)
-            { Stroke = StrokeColor.Value, FillPaintable = FillColor.Value, StrokeWidth = StrokeWidth.Value };
+            { Stroke = StrokeColor.Value, FillPaintable = FillColor.Value, StrokeWidth = (float)StrokeWidth.Value };
     }
 
     public override Node CreateCopy() => new EllipseNode();

+ 25 - 2
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/ConversionTable.cs

@@ -16,34 +16,57 @@ public static class ConversionTable
             {
                 typeof(double), [
                     (typeof(int), new TypeConverter<double, int>(DoubleToInt)),
+                    (typeof(float), new TypeConverter<double, float>(d => (float)d)),
                     (typeof(VecD), new TypeConverter<double, VecD>(DoubleToVecD)),
-                    (typeof(VecI), new TypeConverter<double, VecI>(DoubleToVecI))
+                    (typeof(VecI), new TypeConverter<double, VecI>(DoubleToVecI)),
+                    (typeof(Vec3D), new TypeConverter<double, Vec3D>(d => new Vec3D(d, d, d))),
                 ]
             },
             {
                 typeof(int), [
                     (typeof(double), new TypeConverter<int, double>(ConvertIntToDouble)),
+                    (typeof(float), new TypeConverter<int, float>(i => (float)i)),
                     (typeof(VecI), new TypeConverter<int, VecI>(IntToVecI)),
                     (typeof(VecD), new TypeConverter<int, VecD>(IntToVecD)),
+                    (typeof(Vec3D), new TypeConverter<int, Vec3D>(i => new Vec3D(i, i, i))),
                 ]
             },
             {
                 typeof(VecD), [
                     (typeof(double), new TypeConverter<VecD, double>(VecDToDouble)),
                     (typeof(int), new TypeConverter<VecD, int>(VecDToInt)),
+                    (typeof(float), new TypeConverter<VecD, float>(v => (float)v.X)),
                     (typeof(VecI), new TypeConverter<VecD, VecI>(VecDToVecI)),
+                    (typeof(Vec3D), new TypeConverter<VecD, Vec3D>(v => new Vec3D(v.X, v.Y, v.Y)))
                 ]
             },
             {
                 typeof(VecI), [
                     (typeof(double), new TypeConverter<VecI, double>(VecIToDouble)),
                     (typeof(int), new TypeConverter<VecI, int>(VecIToInt)),
-                    (typeof(VecD), new TypeConverter<VecI, VecD>(VecIToVecD))
+                    (typeof(float), new TypeConverter<VecI, float>(v => v.X)),
+                    (typeof(VecD), new TypeConverter<VecI, VecD>(VecIToVecD)),
+                    (typeof(Vec3D), new TypeConverter<VecI, Vec3D>(v => new Vec3D(v.X, v.Y, v.Y)))
                 ]
             },
             {
                 typeof(Color), [
                     (typeof(Paintable), new TypeConverter<Color, Paintable>(c => new ColorPaintable(c))),
+                    (typeof(VecD), new TypeConverter<Color, VecD>(c => new VecD(c.R, c.G))),
+                    (typeof(VecI), new TypeConverter<Color, VecI>(c => new VecI(c.R, c.G))),
+                    (typeof(Vec3D), new TypeConverter<Color, Vec3D>(c => new Vec3D(c.R, c.G, c.B))),
+                    (typeof(double), new TypeConverter<Color, double>(c => c.R)),
+                    (typeof(int), new TypeConverter<Color, int>(c => c.R)),
+                    (typeof(float), new TypeConverter<Color, float>(c => c.R)),
+                ]
+            },
+            {
+                typeof(Vec3D),[
+                    (typeof(double), new TypeConverter<Vec3D, double>(v => v.X)),
+                    (typeof(int), new TypeConverter<Vec3D, int>(v => (int)v.X)),
+                    (typeof(VecD), new TypeConverter<Vec3D, VecD>(v => new VecD(v.X, v.Y))),
+                    (typeof(VecI), new TypeConverter<Vec3D, VecI>(v => new VecI((int)v.X, (int)v.Y))),
+                    (typeof(Color), new TypeConverter<Vec3D, Color>(v => new Color((byte)Math.Clamp(v.X, 0, 255), (byte)Math.Clamp(v.Y, 0, 255), (byte)Math.Clamp(v.Z, 0, 255)))),
                 ]
             }
         };

+ 6 - 1
src/PixiEditor.SVG/Elements/SvgPolyline.cs

@@ -83,6 +83,11 @@ public class SvgPolyline() : SvgPrimitive("polyline")
 
     private static double ParseNumber(string currentNumberString)
     {
-        return double.Parse(currentNumberString, System.Globalization.CultureInfo.InvariantCulture);
+        if (double.TryParse(currentNumberString, System.Globalization.CultureInfo.InvariantCulture, out var parsed))
+        {
+            return parsed;
+        }
+
+        return 0;
     }
 }

+ 1 - 1
src/PixiEditor.UI.Common/Accents/Base.axaml

@@ -51,7 +51,7 @@
             <Color x:Key="FilterSocketColor">#CC5C5C</Color>
             <Color x:Key="BoolSocketColor">#68abdf</Color>
             <Color x:Key="FloatSocketColor">#ffc66d</Color>
-            <Color x:Key="DoubleSocketColor">#efb66d</Color>
+            <Color x:Key="DoubleSocketColor">#eea55c</Color>
             <Color x:Key="ColorSocketColor">#8cf2dd</Color>
             <Color x:Key="PaintableSocketColor">#48b099</Color>
             <Color x:Key="VecDSocketColor">#c984ca</Color>

+ 35 - 0
src/PixiEditor/Models/Serialization/Factories/VecD4SerializationFactory.cs

@@ -0,0 +1,35 @@
+using Drawie.Numerics;
+
+namespace PixiEditor.Models.Serialization.Factories;
+
+public class VecD4SerializationFactory : SerializationFactory<byte[], Vec4D>
+{
+    public override string DeserializationId { get; } = "PixiEditor.VecD4";
+
+    public override byte[] Serialize(Vec4D original)
+    {
+        byte[] result = new byte[sizeof(double) * 4];
+        BitConverter.GetBytes(original.X).CopyTo(result, 0);
+        BitConverter.GetBytes(original.Y).CopyTo(result, sizeof(double));
+        BitConverter.GetBytes(original.Z).CopyTo(result, sizeof(double) * 2);
+        BitConverter.GetBytes(original.W).CopyTo(result, sizeof(double) * 3);
+        return result;
+    }
+
+    public override bool TryDeserialize(object serialized, out Vec4D original,
+        (string serializerName, string serializerVersion) serializerData)
+    {
+        if (serialized is byte[] { Length: sizeof(double) * 4 } bytes)
+        {
+            original = new Vec4D(
+                BitConverter.ToDouble(bytes, 0),
+                BitConverter.ToDouble(bytes, sizeof(double)),
+                BitConverter.ToDouble(bytes, sizeof(double) * 2),
+                BitConverter.ToDouble(bytes, sizeof(double) * 3));
+            return true;
+        }
+
+        original = default;
+        return false; 
+    }
+}

+ 3 - 0
src/PixiEditor/ViewLocator.cs

@@ -4,9 +4,11 @@ using Avalonia.Controls.Templates;
 using CommunityToolkit.Mvvm.ComponentModel;
 using PixiDocks.Core.Docking;
 using PixiEditor.ViewModels.Dock;
+using PixiEditor.ViewModels.Nodes.Properties;
 using PixiEditor.ViewModels.SubViewModels;
 using PixiEditor.Views.Dock;
 using PixiEditor.Views.Layers;
+using PixiEditor.Views.Nodes.Properties;
 
 namespace PixiEditor;
 
@@ -17,6 +19,7 @@ public class ViewLocator : IDataTemplate
         [typeof(ViewportWindowViewModel)] = typeof(DocumentTemplate),
         [typeof(LazyViewportWindowViewModel)] = typeof(LazyDocumentTemplate),
         [typeof(LayersDockViewModel)] = typeof(LayersManager),
+        [typeof(SinglePropertyViewModel)] = typeof(DoublePropertyView),
     };
 
     public Control Build(object? data)

+ 5 - 1
src/PixiEditor/ViewModels/Document/DocumentManagerViewModel.cs

@@ -92,7 +92,8 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>, IDocument
 
     [Command.Basic("PixiEditor.Document.FlipLayersHorizontal", FlipType.Horizontal, "FLIP_LAYERS_HORIZONTALLY",
         "FLIP_LAYERS_HORIZONTALLY", CanExecute = "PixiEditor.HasDocument",
-        MenuItemPath = "LAYER/FLIP/FLIP_LAYERS_HORIZONTALLY", MenuItemOrder = 16, Icon = PixiPerfectIcons.MirrorHorizontal,
+        MenuItemPath = "LAYER/FLIP/FLIP_LAYERS_HORIZONTALLY", MenuItemOrder = 16,
+        Icon = PixiPerfectIcons.MirrorHorizontal,
         AnalyticsTrack = true)]
     [Command.Basic("PixiEditor.Document.FlipLayersVertical", FlipType.Vertical, "FLIP_LAYERS_VERTICALLY",
         "FLIP_LAYERS_VERTICALLY", CanExecute = "PixiEditor.HasDocument",
@@ -195,6 +196,9 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>, IDocument
         MenuItemPath = "EDIT/DELETE_SELECTED", MenuItemOrder = 6, AnalyticsTrack = true)]
     public void DeleteSelected()
     {
+        if (ActiveDocument is null)
+            return;
+
         if (ActiveDocument.SelectionPathBindable is { IsEmpty: false })
         {
             Owner.DocumentManagerSubViewModel.ActiveDocument?.Operations.DeleteSelectedPixels(activeDocument

+ 1 - 1
src/PixiEditor/ViewModels/Document/Nodes/LerpColorNodeViewModel.cs

@@ -4,5 +4,5 @@ using PixiEditor.ViewModels.Nodes;
 
 namespace PixiEditor.ViewModels.Document.Nodes;
 
-[NodeViewModel("LERP_NODE", "NUMBERS", PixiPerfectIcons.SunCold)]
+[NodeViewModel("LERP_NODE", "COLOR", PixiPerfectIcons.SunCold)]
 internal class LerpColorNodeViewModel : NodeViewModel<LerpColorNode>;

+ 3 - 1
src/PixiEditor/ViewModels/Nodes/NodePropertyViewModel.cs

@@ -179,7 +179,9 @@ internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHand
             propertyType = type.GetMethod("Invoke").ReturnType.BaseType.GenericTypeArguments[0];
         }
 
-        string name = $"{propertyType.Name}PropertyViewModel";
+        string typeName = propertyType.Name;
+
+        string name = $"{typeName}PropertyViewModel";
 
         Type viewModelType = Type.GetType($"PixiEditor.ViewModels.Nodes.Properties.{name}");
         if (viewModelType == null)

+ 59 - 0
src/PixiEditor/ViewModels/Nodes/Properties/SinglePropertyViewModel.cs

@@ -0,0 +1,59 @@
+using System.ComponentModel;
+using Avalonia;
+using Avalonia.Media;
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace PixiEditor.ViewModels.Nodes.Properties;
+
+internal class SinglePropertyViewModel : NodePropertyViewModel<float>
+{
+    private double min = double.MinValue;
+    private double max = double.MaxValue;
+
+    private NumberPickerMode numberPickerMode = NumberPickerMode.NumberInput;
+
+    private SliderSettings sliderSettings = new SliderSettings();
+
+    public NumberPickerMode NumberPickerMode
+    {
+        get => numberPickerMode;
+        set => SetProperty(ref numberPickerMode, value);
+    }
+
+    public double DoubleValue
+    {
+        get => Value;
+        set => Value = (float)value;
+    }
+
+    public double Min
+    {
+        get => min;
+        set => SetProperty(ref min, value);
+    }
+
+    public double Max
+    {
+        get => max;
+        set => SetProperty(ref max, value);
+    }
+
+    public SliderSettings SliderSettings
+    {
+        get => sliderSettings;
+        set => SetProperty(ref sliderSettings, value);
+    }
+
+    public SinglePropertyViewModel(NodeViewModel node, Type valueType) : base(node, valueType)
+    {
+    }
+
+    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
+    {
+        base.OnPropertyChanged(e);
+        if (e.PropertyName == nameof(Value))
+        {
+            OnPropertyChanged(nameof(DoubleValue));
+        }
+    }
+}

+ 7 - 0
src/PixiEditor/Views/Nodes/Properties/ColorMatrixPropertyView.axaml.cs

@@ -1,5 +1,6 @@
 using Avalonia;
 using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
 using Avalonia.Markup.Xaml;
 
 namespace PixiEditor.Views.Nodes.Properties;
@@ -10,5 +11,11 @@ public partial class ColorMatrixPropertyView : NodePropertyView
     {
         InitializeComponent();
     }
+
+    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+    {
+        base.OnApplyTemplate(e);
+        HideSocket(true, false);
+    }
 }
 

+ 7 - 0
src/PixiEditor/Views/Nodes/Properties/GenericEnumPropertyView.axaml.cs

@@ -1,5 +1,6 @@
 using Avalonia;
 using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
 using Avalonia.Input;
 using Avalonia.Markup.Xaml;
 
@@ -12,6 +13,12 @@ public partial class GenericEnumPropertyView : NodePropertyView
         InitializeComponent();
     }
 
+    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+    {
+        base.OnApplyTemplate(e);
+        HideSocket(true, false);
+    }
+
     private void InputElement_OnPointerPressed(object? sender, PointerPressedEventArgs e)
     {
         e.Handled = true;

+ 7 - 0
src/PixiEditor/Views/Nodes/Properties/KernelPropertyView.axaml.cs

@@ -1,5 +1,6 @@
 using Avalonia;
 using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
 using Avalonia.Markup.Xaml;
 
 namespace PixiEditor.Views.Nodes.Properties;
@@ -9,5 +10,11 @@ public partial class KernelPropertyView : NodePropertyView
     {
         InitializeComponent();
     }
+
+    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+    {
+        base.OnApplyTemplate(e);
+        HideSocket(true, false);
+    }
 }
 

+ 7 - 0
src/PixiEditor/Views/Nodes/Properties/Matrix4x5FPropertyView.axaml.cs

@@ -1,5 +1,6 @@
 using Avalonia;
 using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
 using Avalonia.Markup.Xaml;
 
 namespace PixiEditor.Views.Nodes.Properties;
@@ -10,5 +11,11 @@ public partial class Matrix4x5FPropertyView : NodePropertyView
     {
         InitializeComponent();
     }
+
+    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+    {
+        base.OnApplyTemplate(e);
+        HideSocket(true, false);
+    }
 }