瀏覽代碼

Merge branch 'master' into brush-engine

Krzysztof Krysiński 2 周之前
父節點
當前提交
256c87bc14

+ 1 - 1
README.md

@@ -8,7 +8,7 @@
 
 <img width="50%" align="center" src="https://github.com/user-attachments/assets/bd08c8bd-f610-449d-b1e2-6a990e562518">
 
-## The only 2D Graphics Editor you'll ever need
+## The All-in-One Editor for 2D
 
 **PixiEditor** is a universal 2D editor that was made to provide you with tools and features for all your 2D needs. Create beautiful sprites for your games, animations, edit images, create logos. All packed up in an intuitive and familiar interface.
 

+ 32 - 5
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PathVectorData.cs

@@ -11,11 +11,37 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 public class PathVectorData : ShapeVectorData, IReadOnlyPathData
 {
     public VectorPath Path { get; set; }
-    public override RectD GeometryAABB => Path?.TightBounds ?? RectD.Empty;
+
+    public override RectD GeometryAABB
+    {
+        get
+        {
+            var tightBounds = Path?.TightBounds ?? RectD.Empty;
+            if (tightBounds.Width == 0 || tightBounds.Height == 0)
+            {
+                // If the path is a line or a point, we need to inflate the bounds by half the stroke width
+                double halfStroke = StrokeWidth / 2;
+                tightBounds = new RectD(
+                    tightBounds.X - halfStroke,
+                    tightBounds.Y - halfStroke,
+                    tightBounds.Width + StrokeWidth,
+                    tightBounds.Height + StrokeWidth);
+            }
+
+            return tightBounds;
+        }
+    }
+
     public override RectD VisualAABB => GeometryAABB.Inflate(StrokeWidth / 2);
 
-    public override ShapeCorners TransformationCorners =>
-        new ShapeCorners(Path.TightBounds).WithMatrix(TransformationMatrix);
+    public override ShapeCorners TransformationCorners
+    {
+        get
+        {
+            var tightCorners = new ShapeCorners(GeometryAABB);
+            return tightCorners.WithMatrix(TransformationMatrix);
+        }
+    }
 
     public StrokeCap StrokeLineCap { get; set; } = StrokeCap.Round;
 
@@ -133,8 +159,9 @@ public class PathVectorData : ShapeVectorData, IReadOnlyPathData
 
     protected bool Equals(PathVectorData other)
     {
-        return base.Equals(other) && Path.Equals(other.Path) && StrokeLineCap == other.StrokeLineCap && StrokeLineJoin == other.StrokeLineJoin
-                && FillType == other.FillType;
+        return base.Equals(other) && Path.Equals(other.Path) && StrokeLineCap == other.StrokeLineCap &&
+               StrokeLineJoin == other.StrokeLineJoin
+               && FillType == other.FillType;
     }
 
     public override bool Equals(object? obj)

+ 29 - 17
src/PixiEditor.ChangeableDocument/Changes/Selection/MagicWand/MagicWand_Change.cs → src/PixiEditor.ChangeableDocument/Changes/Selection/MagicWand/MagicWand_UpdateableChange.cs

@@ -6,7 +6,7 @@ using Drawie.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changes.Selection.MagicWand;
 
-internal class MagicWand_Change : Change
+internal class MagicWand_UpdateableChange : UpdateableChange
 {
     private VectorPath? originalPath;
     private VectorPath path = new() { FillType = PathFillType.EvenOdd };
@@ -16,8 +16,8 @@ internal class MagicWand_Change : Change
     private int frame;
     private double tolerance;
 
-    [GenerateMakeChangeAction]
-    public MagicWand_Change(List<Guid> memberGuids, VecI point, SelectionMode mode, double tolerance, int frame)
+    [GenerateUpdateableChangeActions]
+    public MagicWand_UpdateableChange(List<Guid> memberGuids, VecI point, SelectionMode mode, double tolerance, int frame)
     {
         path.MoveTo(point);
         this.mode = mode;
@@ -33,7 +33,24 @@ internal class MagicWand_Change : Change
         return true;
     }
 
+    [UpdateChangeMethod]
+    public void Update(VecI point)
+    {
+        this.point = point;
+    }
+
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
+    {
+        ignoreInUndo = false;
+        return CommonApply(target);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> ApplyTemporarily(Document target)
+    {
+        return CommonApply(target);
+    }
+
+    private Selection_ChangeInfo CommonApply(Document target)
     {
         HashSet<Guid> membersToReference = new();
 
@@ -45,19 +62,6 @@ internal class MagicWand_Change : Change
 
         path = MagicWandHelper.DoMagicWandFloodFill(point, membersToReference, tolerance, target, frame);
 
-        ignoreInUndo = false;
-        return CommonApply(target);
-    }
-
-    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
-    {
-        (var toDispose, target.Selection.SelectionPath) = (target.Selection.SelectionPath, new VectorPath(originalPath!));
-        toDispose.Dispose();
-        return new Selection_ChangeInfo(new VectorPath(target.Selection.SelectionPath));
-    }
-
-    private Selection_ChangeInfo CommonApply(Document target)
-    {
         var toDispose = target.Selection.SelectionPath;
         if (mode == SelectionMode.New)
         {
@@ -67,13 +71,21 @@ internal class MagicWand_Change : Change
         }
         else
         {
-            target.Selection.SelectionPath = originalPath!.Op(path, mode.ToVectorPathOp());
+            target.Selection.SelectionPath = target.Selection.SelectionPath!.Op(path, mode.ToVectorPathOp());
         }
         toDispose.Dispose();
 
         return new Selection_ChangeInfo(new VectorPath(target.Selection.SelectionPath));
     }
 
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        var toDispose = target.Selection.SelectionPath;
+        target.Selection.SelectionPath = new VectorPath(originalPath!);
+        toDispose.Dispose();
+        return new Selection_ChangeInfo(new VectorPath(target.Selection.SelectionPath));
+    }
+
     public override void Dispose()
     {
         path.Dispose();

+ 3 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -312,7 +312,9 @@
   "LINE_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to draw a line. Hold Shift to enable snapping.",
   "LINE_TOOL_ACTION_DISPLAY_SHIFT": "Click and move mouse to draw a line with snapping enabled.",
   "MAGIC_WAND_TOOL_TOOLTIP": "Magic Wand ({0}). Flood's the selection",
-  "MAGIC_WAND_ACTION_DISPLAY": "Click to flood the selection.",
+  "MAGIC_WAND_ACTION_DISPLAY": "Click to flood the selection. Hold Shift to add to existing selection. Hold Ctrl to subtract from it.",
+  "MAGIC_WAND_ACTION_DISPLAY_SHIFT": "Click to add to the current selection.",
+  "MAGIC_WAND_ACTION_DISPLAY_CTRL": "Click to subtract from the current selection.",
   "PEN_TOOL": "Pen",
   "BRIGHTNESS_TOOL": "Brightness",
   "COLOR_PICKER_TOOL": "Color Picker",

+ 19 - 4
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/MagicWandToolExecutor.cs

@@ -25,7 +25,7 @@ internal class MagicWandToolExecutor : UpdateableChangeExecutor
         if (magicWand is null || members.Count == 0)
             return ExecutionState.Error;
 
-        mode = magicWand.SelectMode;
+        mode = magicWand.ResultingSelectionMode;
         memberGuids = members;
         considerAllLayers = magicWand.DocumentScope == DocumentScope.Canvas;
         if (considerAllLayers)
@@ -33,19 +33,34 @@ internal class MagicWandToolExecutor : UpdateableChangeExecutor
         var pos = controller!.LastPixelPosition;
         tolerance = (float)magicWand.Tolerance;
 
-        internals!.ActionAccumulator.AddActions(new MagicWand_Action(memberGuids, pos, mode, tolerance, document!.AnimationHandler.ActiveFrameBindable));
+        AddUpdateAction(pos);
 
         return ExecutionState.Success;
     }
 
+    public override void OnPixelPositionChange(VecI pos)
+    {
+        AddUpdateAction(pos);
+    }
+
     public override void OnLeftMouseButtonUp(VecD argsPositionOnCanvas)
     {
-        internals!.ActionAccumulator.AddActions(new ChangeBoundary_Action());
+        AddFinishAction();
         onEnded!(this);
     }
 
     public override void ForceStop()
     {
-        internals!.ActionAccumulator.AddActions(new ChangeBoundary_Action());
+        AddFinishAction();
+    }
+
+    private void AddUpdateAction(VecI pos)
+    {
+        var action = new MagicWand_Action(memberGuids, pos, mode, tolerance, document!.AnimationHandler.ActiveFrameBindable);
+        internals!.ActionAccumulator.AddActions(action);
+    }
+    private void AddFinishAction()
+    {
+        internals!.ActionAccumulator.AddFinishedActions(new EndMagicWand_Action());
     }
 }

+ 1 - 1
src/PixiEditor/Models/Handlers/Tools/IMagicWandToolHandler.cs

@@ -5,7 +5,7 @@ namespace PixiEditor.Models.Handlers.Tools;
 
 internal interface IMagicWandToolHandler : IToolHandler
 {
-    public SelectionMode SelectMode { get; }
+    public SelectionMode ResultingSelectionMode { get; }
     public DocumentScope DocumentScope { get; }
     public float Tolerance { get; }
 }

+ 23 - 3
src/PixiEditor/ViewModels/Tools/Tools/MagicWandToolViewModel.cs

@@ -18,10 +18,11 @@ namespace PixiEditor.ViewModels.Tools.Tools;
 internal class MagicWandToolViewModel : ToolViewModel, IMagicWandToolHandler
 {
     public override LocalizedString Tooltip => new LocalizedString("MAGIC_WAND_TOOL_TOOLTIP", Shortcut);
-
+    private LocalizedString defaultActionDisplay => new LocalizedString("MAGIC_WAND_ACTION_DISPLAY");
     public override string ToolNameLocalizationKey => "MAGIC_WAND_TOOL";
     public override Type[]? SupportedLayerTypes { get; } = { typeof(IRasterLayerHandler) };
-
+    private SelectionMode KeyModifierSelectionMode = SelectionMode.New;
+    public SelectionMode ResultingSelectionMode => KeyModifierSelectionMode != SelectionMode.New ? KeyModifierSelectionMode : SelectMode;
     [Settings.Enum("MODE_LABEL")]
     public SelectionMode SelectMode => GetValue<SelectionMode>();
 
@@ -36,7 +37,7 @@ internal class MagicWandToolViewModel : ToolViewModel, IMagicWandToolHandler
     public MagicWandToolViewModel()
     {
         Toolbar = ToolbarFactory.Create(this);
-        ActionDisplay = "MAGIC_WAND_ACTION_DISPLAY";
+        ActionDisplay = defaultActionDisplay;
     }
 
     public override Type LayerTypeToCreateOnEmptyUse { get; } = null;
@@ -45,4 +46,23 @@ internal class MagicWandToolViewModel : ToolViewModel, IMagicWandToolHandler
     {
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseMagicWandTool();
     }
+
+    public override void KeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown, Key argsKey)
+    {
+        if (shiftIsDown)
+        {
+            ActionDisplay = new LocalizedString("MAGIC_WAND_ACTION_DISPLAY_SHIFT");
+            KeyModifierSelectionMode = SelectionMode.Add;
+        }
+        else if (ctrlIsDown)
+        {
+            ActionDisplay = new LocalizedString("MAGIC_WAND_ACTION_DISPLAY_CTRL");
+            KeyModifierSelectionMode = SelectionMode.Subtract;
+        }
+        else
+        {
+            ActionDisplay = defaultActionDisplay;
+            KeyModifierSelectionMode = SelectionMode.New;
+        }
+    }
 }