Browse Source

Added smooth circle brush for painting toolset

flabbet 9 months ago
parent
commit
061a4f7be2

+ 2 - 1
src/PixiEditor/Data/Configs/ToolSetsConfig.json

@@ -41,7 +41,8 @@
         "Settings": {
           "AntiAliasing": true,
           "ExposeHardness": true,
-          "ExposeSpacing": true
+          "ExposeSpacing": true,
+          "BrushShapeSetting": "CircleSmooth" 
         }
       },
       "Select",

+ 24 - 1
src/PixiEditor/ViewModels/Tools/ToolSettings/Settings/EnumSettingViewModel.cs

@@ -32,7 +32,7 @@ internal sealed class EnumSettingViewModel<TEnum> : Setting<TEnum, ComboBox>
     /// </summary>
     public override TEnum Value
     {
-        get => Enum.GetValues<TEnum>()[SelectedIndex];
+        get => hasOverwrittenValue ? GetOverwrittenEnum() : Enum.GetValues<TEnum>()[SelectedIndex];
         set
         {
             var values = Enum.GetValues<TEnum>();
@@ -63,4 +63,27 @@ internal sealed class EnumSettingViewModel<TEnum> : Setting<TEnum, ComboBox>
     {
         Value = defaultValue;
     }
+    
+    private TEnum GetOverwrittenEnum()
+    {
+        int index;
+        if (overwrittenValue is float floatVal)
+        {
+            index = (int)floatVal;
+        }
+        else if (overwrittenValue is int intVal)
+        {
+            index = intVal;
+        }
+        else if (overwrittenValue is string stringVal)
+        {
+            return Enum.Parse<TEnum>(stringVal);
+        }
+        else
+        {
+            throw new InvalidCastException("Overwritten value is not a valid type.");
+        }
+
+        return Enum.GetValues<TEnum>()[index];
+    }
 }

+ 13 - 4
src/PixiEditor/ViewModels/Tools/ToolSettings/Settings/Setting.cs

@@ -1,5 +1,6 @@
 using Avalonia.Controls;
 using CommunityToolkit.Mvvm.ComponentModel;
+using DiscordRPC;
 using PixiEditor.Extensions.Common.Localization;
 
 #pragma warning disable SA1402 // File may only contain a single type, Justification: "Same class with generic value"
@@ -49,11 +50,11 @@ internal abstract class Setting : ObservableObject
     private object _value;
     private bool isExposed = true;
     
-    private bool overwrittenExposed;
-    private object overwrittenValue;
+    protected bool overwrittenExposed;
+    protected object overwrittenValue;
 
-    private bool hasOverwrittenValue;
-    private bool hasOverwrittenExposed;
+    protected bool hasOverwrittenValue;
+    protected bool hasOverwrittenExposed;
     
     protected Setting(string name)
     {
@@ -101,6 +102,7 @@ internal abstract class Setting : ObservableObject
         hasOverwrittenValue = true;
         
         OnPropertyChanged(nameof(Value));
+        ValueChanged?.Invoke(this, new SettingValueChangedEventArgs<object>(_value, value));
     }
     
     public void SetOverwriteExposed(bool value)
@@ -113,6 +115,8 @@ internal abstract class Setting : ObservableObject
     
     public void ResetOverwrite()
     {
+        var old = overwrittenValue;
+        bool hadOverwrittenValue = hasOverwrittenValue;
         overwrittenValue = null;
         overwrittenExposed = false;
         hasOverwrittenValue = false;
@@ -120,5 +124,10 @@ internal abstract class Setting : ObservableObject
         
         OnPropertyChanged(nameof(Value));
         OnPropertyChanged(nameof(IsExposed));
+
+        if (hadOverwrittenValue)
+        {
+            ValueChanged?.Invoke(this, new SettingValueChangedEventArgs<object>(old, _value));
+        }
     }
 }

+ 1 - 1
src/PixiEditor/ViewModels/Tools/Tools/BrightnessToolViewModel.cs

@@ -29,7 +29,7 @@ internal class BrightnessToolViewModel : ToolViewModel, IBrightnessToolHandler
     public override bool IsErasable => true;
     public override LocalizedString Tooltip => new LocalizedString("BRIGHTNESS_TOOL_TOOLTIP", Shortcut);
 
-    public override BrushShape BrushShape => BrushShape.Circle;
+    public override BrushShape BrushShape => BrushShape.CirclePixelated;
 
     public override string DefaultIcon => PixiPerfectIcons.Sun;
 

+ 1 - 1
src/PixiEditor/ViewModels/Tools/Tools/EraserToolViewModel.cs

@@ -27,7 +27,7 @@ internal class EraserToolViewModel : ToolViewModel, IEraserToolHandler
     public override bool IsErasable => true;
 
     public override string ToolNameLocalizationKey => "ERASER_TOOL";
-    public override BrushShape BrushShape => BrushShape.Circle;
+    public override BrushShape BrushShape => BrushShape.CirclePixelated;
     public override Type[]? SupportedLayerTypes { get; } =
     {
         typeof(IRasterLayerHandler)

+ 9 - 1
src/PixiEditor/ViewModels/Tools/Tools/PenToolViewModel.cs

@@ -20,7 +20,7 @@ namespace PixiEditor.ViewModels.Tools.Tools
         private int actualToolSize;
 
         public override string ToolNameLocalizationKey => "PEN_TOOL";
-        public override BrushShape BrushShape => BrushShape.Circle;
+        public override BrushShape BrushShape => BrushShapeSetting;
         
         public override Type[]? SupportedLayerTypes { get; } = { typeof(IRasterLayerHandler) };
 
@@ -39,6 +39,9 @@ namespace PixiEditor.ViewModels.Tools.Tools
 
         [Settings.Bool("PIXEL_PERFECT_SETTING", Notify = nameof(PixelPerfectChanged), ExposedByDefault = false)]
         public bool PixelPerfectEnabled => GetValue<bool>();
+        
+        [Settings.Enum("BRUSH_SHAPE_SETTING", BrushShape.CirclePixelated, ExposedByDefault = false, Notify = nameof(BrushShapeChanged))]
+        public BrushShape BrushShapeSetting => GetValue<BrushShape>();
 
         public override string DefaultIcon => PixiPerfectIcons.Pen;
 
@@ -106,5 +109,10 @@ namespace PixiEditor.ViewModels.Tools.Tools
                 setting.Value = actualToolSize;
             }
         }
+        
+        private void BrushShapeChanged()
+        {
+            OnPropertyChanged(nameof(BrushShape));
+        }
     }
 }

+ 2 - 1
src/PixiEditor/Views/Overlays/BrushShapeOverlay/BrushShape.cs

@@ -4,5 +4,6 @@ internal enum BrushShape
     Hidden,
     Pixel,
     Square,
-    Circle
+    CirclePixelated,
+    CircleSmooth
 }

+ 22 - 5
src/PixiEditor/Views/Overlays/BrushShapeOverlay/BrushShapeOverlay.cs

@@ -17,7 +17,8 @@ internal class BrushShapeOverlay : Overlay
         AvaloniaProperty.Register<BrushShapeOverlay, int>(nameof(BrushSize), defaultValue: 1);
 
     public static readonly StyledProperty<BrushShape> BrushShapeProperty =
-        AvaloniaProperty.Register<BrushShapeOverlay, BrushShape>(nameof(BrushShape), defaultValue: BrushShape.Circle);
+        AvaloniaProperty.Register<BrushShapeOverlay, BrushShape>(nameof(BrushShape),
+            defaultValue: BrushShape.CirclePixelated);
 
     public static readonly StyledProperty<Scene> SceneProperty = AvaloniaProperty.Register<BrushShapeOverlay, Scene>(
         nameof(Scene));
@@ -40,7 +41,7 @@ internal class BrushShapeOverlay : Overlay
         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 VectorPath threePixelCircle;
@@ -65,7 +66,7 @@ internal class BrushShapeOverlay : Overlay
             return;
 
         VecD rawPoint = args.Point;
-        lastMousePos = rawPoint; 
+        lastMousePos = rawPoint;
         Refresh();
     }
 
@@ -80,6 +81,7 @@ internal class BrushShapeOverlay : Overlay
         switch (BrushShape)
         {
             case BrushShape.Pixel:
+                paint.IsAntiAliased = false;
                 targetCanvas.DrawRect(
                     new RectD(new VecD(Math.Floor(lastMousePos.X), Math.Floor(lastMousePos.Y)), new VecD(1, 1)),
                     paint);
@@ -87,14 +89,19 @@ internal class BrushShapeOverlay : Overlay
             case BrushShape.Square:
                 targetCanvas.DrawRect(winRect, paint);
                 break;
-            case BrushShape.Circle:
+            case BrushShape.CirclePixelated:
                 DrawCircleBrushShape(targetCanvas, winRect);
                 break;
+            case BrushShape.CircleSmooth:
+                DrawCircleBrushShapeSmooth(targetCanvas, lastMousePos, BrushSize / 2f);
+                break;
         }
     }
 
     private void DrawCircleBrushShape(Canvas drawingContext, RectD winRect)
     {
+        paint.IsAntiAliased = false;
+        
         var rectI = new RectI((int)winRect.X, (int)winRect.Y, (int)winRect.Width, (int)winRect.Height);
         if (BrushSize < 3)
         {
@@ -124,11 +131,21 @@ internal class BrushShapeOverlay : Overlay
 
             var lp = new VecI((int)lastMousePos.X, (int)lastMousePos.Y);
             using VectorPath shifted = new VectorPath(lastNonTranslatedCircle);
-            shifted.Transform(Matrix3X3.CreateTranslation(lp.X - rectI.Width / 2, lp.Y - rectI.Height / 2)); // don't use float, truncation is intended 
+            shifted.Transform(Matrix3X3.CreateTranslation(lp.X - rectI.Width / 2,
+                lp.Y - rectI.Height / 2)); // don't use float, truncation is intended 
             drawingContext.DrawPath(shifted, paint);
         }
     }
 
+    private void DrawCircleBrushShapeSmooth(Canvas drawingContext, VecD lastMousePos, float radius)
+    {
+        VecD center = lastMousePos; 
+        paint.IsAntiAliased = true;
+        
+        drawingContext.DrawOval(new VecD(center.X, center.Y), new VecD(radius, radius),
+            paint);
+    }
+
     protected override void ZoomChanged(double newZoom)
     {
         paint.StrokeWidth = (float)(1.0f / newZoom);