Browse Source

Merge pull request #1281 from PixiEditor/fixes-and-right-click-mode

Various fixes
Krzysztof Krysiński 6 days ago
parent
commit
da452c90f2
31 changed files with 306 additions and 106 deletions
  1. 14 7
      src/PixiEditor.ChangeableDocument/Changeables/Brushes/BrushEngine.cs
  2. 4 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Brushes/BrushOutputNode.cs
  3. 6 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/PointerInfoNode.cs
  4. 5 1
      src/PixiEditor.ChangeableDocument/Rendering/ContextData/PointerInfo.cs
  5. 16 0
      src/PixiEditor.UI.Common/Controls/SelectableStrip.cs
  6. BIN
      src/PixiEditor/Data/BrushTools/Brightness.pixi
  7. BIN
      src/PixiEditor/Data/Brushes/PixelCircle.pixi
  8. BIN
      src/PixiEditor/Data/Brushes/PixelSquare.pixi
  9. 3 1
      src/PixiEditor/Data/Configs/ToolSetsConfig.json
  10. 3 1
      src/PixiEditor/Data/Localization/Languages/en.json
  11. 3 0
      src/PixiEditor/Models/Config/ToolsConfig.cs
  12. 1 0
      src/PixiEditor/Models/Controllers/InputDevice/MouseOnCanvasEventArgs.cs
  13. 18 1
      src/PixiEditor/Models/DocumentModels/ChangeExecutionController.cs
  14. 2 0
      src/PixiEditor/Models/DocumentModels/Public/DocumentEventsModule.cs
  15. 6 0
      src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs
  16. 2 2
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/BrushBasedExecutor.cs
  17. 8 1
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/EraserToolExecutor.cs
  18. 2 0
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/UpdateableChangeExecutor.cs
  19. 3 0
      src/PixiEditor/Models/Handlers/IToolsHandler.cs
  20. 1 1
      src/PixiEditor/ViewModels/Document/DocumentManagerViewModel.cs
  21. 22 1
      src/PixiEditor/ViewModels/Document/DocumentViewModel.cs
  22. 40 9
      src/PixiEditor/ViewModels/SubViewModels/IoViewModel.cs
  23. 8 0
      src/PixiEditor/ViewModels/SubViewModels/LayersViewModel.cs
  24. 70 38
      src/PixiEditor/ViewModels/SubViewModels/ToolsViewModel.cs
  25. 18 16
      src/PixiEditor/ViewModels/Tools/ToolSettings/Settings/Setting.cs
  26. 20 9
      src/PixiEditor/ViewModels/Tools/ToolViewModel.cs
  27. 6 2
      src/PixiEditor/ViewModels/Tools/Tools/BrushBasedToolViewModel.cs
  28. 22 9
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml.cs
  29. 1 1
      src/PixiEditor/Views/Overlays/BrushShapeOverlay/BrushShapeOverlay.cs
  30. 1 1
      src/PixiEditor/Views/Rendering/Scene.cs
  31. 1 1
      src/PixiEditor/Views/Tools/ToolSettings/Settings/EnumSettingView.axaml

+ 14 - 7
src/PixiEditor.ChangeableDocument/Changeables/Brushes/BrushEngine.cs

@@ -57,20 +57,31 @@ public class BrushEngine : IDisposable
     /// </summary>
     /// </summary>
     private float GetSmoothedPressure(double targetPressure)
     private float GetSmoothedPressure(double targetPressure)
     {
     {
-        if (pointsHistory.Count == 0)
+        if (pointsHistory.Count <= 0)
             return (float)targetPressure;
             return (float)targetPressure;
 
 
         double sum = 0;
         double sum = 0;
         int count = 0;
         int count = 0;
 
 
-        // Iterate backwards through history
         for (int i = pointsHistory.Count - 1; i >= 0 && count < PressureSmoothingWindowSize; i--)
         for (int i = pointsHistory.Count - 1; i >= 0 && count < PressureSmoothingWindowSize; i--)
         {
         {
             sum += pointsHistory[i].PointerInfo.Pressure;
             sum += pointsHistory[i].PointerInfo.Pressure;
             count++;
             count++;
         }
         }
 
 
-        // Add the current target to the average so we pull towards the new value
+        double historicalAverage = sum / count;
+
+        // If the new pressure is significantly higher than history,
+        // the user is trying to make a bold stroke. Minimize smoothing.
+        if (targetPressure > historicalAverage)
+        {
+            // "Lerp" towards the target.
+            // 0.8f means: "Use 80% raw pressure, 20% historical average"
+            float attackFactor = 0.8f;
+            return (float)(historicalAverage + (targetPressure - historicalAverage) * attackFactor);
+        }
+
+        // If pressure is steady or dropping, use full smoothing to hide jitter.
         sum += targetPressure;
         sum += targetPressure;
         count++;
         count++;
 
 
@@ -259,10 +270,6 @@ 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);
             return;
             return;
         }
         }
 
 

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

@@ -251,7 +251,7 @@ public class BrushOutputNode : Node
             previewEngine.ExecuteBrush(previewChunkyImage,
             previewEngine.ExecuteBrush(previewChunkyImage,
                 new BrushData(context.Graph, Id) { StrokeWidth = size, AntiAliasing = true },
                 new BrushData(context.Graph, Id) { StrokeWidth = size, AntiAliasing = true },
                 (VecI)pos, context.FrameTime, context.ProcessingColorSpace, context.DesiredSamplingOptions,
                 (VecI)pos, context.FrameTime, context.ProcessingColorSpace, context.DesiredSamplingOptions,
-                new PointerInfo(pos, 1, 0, VecD.Zero, new VecD(0, 1)),
+                new PointerInfo(pos, 1, 0, VecD.Zero, new VecD(0, 1), true, false),
                 new KeyboardInfo(),
                 new KeyboardInfo(),
                 new EditorData(Colors.White, Colors.Black));
                 new EditorData(Colors.White, Colors.Black));
         }
         }
@@ -284,7 +284,7 @@ public class BrushOutputNode : Node
             pos = vec4D.XY;
             pos = vec4D.XY;
             pos = new VecD(pos.X, pos.Y + maxSize / 2f) + shift;
             pos = new VecD(pos.X, pos.Y + maxSize / 2f) + shift;
 
 
-            points.Add(new RecordedPoint((VecI)pos, new PointerInfo(pos, pressure, 0, VecD.Zero, vec4D.ZW),
+            points.Add(new RecordedPoint((VecI)pos, new PointerInfo(pos, pressure, 0, VecD.Zero, vec4D.ZW, true, false),
                 new KeyboardInfo(), new EditorData(Colors.White, Colors.Black)));
                 new KeyboardInfo(), new EditorData(Colors.White, Colors.Black)));
 
 
             previewEngine.ExecuteBrush(target,
             previewEngine.ExecuteBrush(target,
@@ -316,7 +316,7 @@ public class BrushOutputNode : Node
             var vec4D = previewVectorPath.GetPositionAndTangentAtDistance(offset, false);
             var vec4D = previewVectorPath.GetPositionAndTangentAtDistance(offset, false);
             pos = vec4D.XY;
             pos = vec4D.XY;
             pos = new VecD(pos.X, pos.Y + maxSize / 2f) + shift;
             pos = new VecD(pos.X, pos.Y + maxSize / 2f) + shift;
-            points.Add(new RecordedPoint((VecI)pos, new PointerInfo(pos, pressure, 0, VecD.Zero, vec4D.ZW),
+            points.Add(new RecordedPoint((VecI)pos, new PointerInfo(pos, pressure, 0, VecD.Zero, vec4D.ZW, true, false),
                 new KeyboardInfo(), new EditorData(Colors.White, Colors.Black)));
                 new KeyboardInfo(), new EditorData(Colors.White, Colors.Black)));
 
 
             previewEngine.ExecuteBrush(target,
             previewEngine.ExecuteBrush(target,
@@ -333,7 +333,7 @@ public class BrushOutputNode : Node
         previewEngine.ExecuteBrush(img,
         previewEngine.ExecuteBrush(img,
             new BrushData(context.Graph, Id) { StrokeWidth = size, AntiAliasing = true },
             new BrushData(context.Graph, Id) { StrokeWidth = size, AntiAliasing = true },
             pos, context.FrameTime, context.ProcessingColorSpace, context.DesiredSamplingOptions,
             pos, context.FrameTime, context.ProcessingColorSpace, context.DesiredSamplingOptions,
-            new PointerInfo(pos, 1, 0, VecD.Zero, new VecD(0, 1)),
+            new PointerInfo(pos, 1, 0, VecD.Zero, new VecD(0, 1), true, false),
             new KeyboardInfo(),
             new KeyboardInfo(),
             new EditorData(Colors.White, Colors.Black));
             new EditorData(Colors.White, Colors.Black));
     }
     }

+ 6 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/PointerInfoNode.cs

@@ -7,6 +7,8 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 [NodeInfo("PointerInfo")]
 [NodeInfo("PointerInfo")]
 public class PointerInfoNode : Node
 public class PointerInfoNode : Node
 {
 {
+    public OutputProperty<bool> IsLeftButtonPressed { get; }
+    public OutputProperty<bool> IsRightButtonPressed { get; }
     public OutputProperty<VecD> PositionOnCanvas { get; }
     public OutputProperty<VecD> PositionOnCanvas { get; }
     public OutputProperty<double> Pressure { get; }
     public OutputProperty<double> Pressure { get; }
     public OutputProperty<double> Twist { get; }
     public OutputProperty<double> Twist { get; }
@@ -17,6 +19,8 @@ public class PointerInfoNode : Node
 
 
     public PointerInfoNode()
     public PointerInfoNode()
     {
     {
+        IsLeftButtonPressed = CreateOutput<bool>("IsLeftButtonPressed", "IS_LEFT_BUTTON_PRESSED", false);
+        IsRightButtonPressed = CreateOutput<bool>("IsRightButtonPressed", "IS_RIGHT_BUTTON_PRESSED", false);
         PositionOnCanvas = CreateOutput<VecD>("PositionOnCanvas", "POSITION_ON_CANVAS", new VecD(0, 0));
         PositionOnCanvas = CreateOutput<VecD>("PositionOnCanvas", "POSITION_ON_CANVAS", new VecD(0, 0));
         Pressure = CreateOutput<double>("Pressure", "PRESSURE", 1.0);
         Pressure = CreateOutput<double>("Pressure", "PRESSURE", 1.0);
         Twist = CreateOutput<double>("Twist", "TWIST", 0.0);
         Twist = CreateOutput<double>("Twist", "TWIST", 0.0);
@@ -32,6 +36,8 @@ public class PointerInfoNode : Node
             return;
             return;
         }
         }
 
 
+        IsLeftButtonPressed.Value = context.PointerInfo.IsLeftButtonPressed;
+        IsRightButtonPressed.Value = context.PointerInfo.IsRightButtonPressed;
         PositionOnCanvas.Value = context.PointerInfo.PositionOnCanvas;
         PositionOnCanvas.Value = context.PointerInfo.PositionOnCanvas;
         Pressure.Value = context.PointerInfo.Pressure;
         Pressure.Value = context.PointerInfo.Pressure;
         Twist.Value = context.PointerInfo.Twist;
         Twist.Value = context.PointerInfo.Twist;

+ 5 - 1
src/PixiEditor.ChangeableDocument/Rendering/ContextData/PointerInfo.cs

@@ -4,6 +4,8 @@ namespace PixiEditor.ChangeableDocument.Rendering.ContextData;
 
 
 public record struct PointerInfo
 public record struct PointerInfo
 {
 {
+    public bool IsLeftButtonPressed { get; set; }
+    public bool IsRightButtonPressed { get; set; }
     public VecD PositionOnCanvas { get; set; }
     public VecD PositionOnCanvas { get; set; }
     public float Pressure { get; set; }
     public float Pressure { get; set; }
     public float Twist { get; set; }
     public float Twist { get; set; }
@@ -11,13 +13,15 @@ public record struct PointerInfo
     public VecD MovementDirection { get; set; }
     public VecD MovementDirection { get; set; }
     public double Rotation { get; set; }
     public double Rotation { get; set; }
 
 
-    public PointerInfo(VecD positionOnCanvas, float pressure, float twist, VecD tilt, VecD movementDirection)
+    public PointerInfo(VecD positionOnCanvas, float pressure, float twist, VecD tilt, VecD movementDirection, bool isLeftButtonPressed, bool isRightButtonPressed)
     {
     {
         PositionOnCanvas = positionOnCanvas;
         PositionOnCanvas = positionOnCanvas;
         Pressure = pressure;
         Pressure = pressure;
         Twist = twist;
         Twist = twist;
         Tilt = tilt;
         Tilt = tilt;
         MovementDirection = movementDirection;
         MovementDirection = movementDirection;
+        IsLeftButtonPressed = isLeftButtonPressed;
+        IsRightButtonPressed = isRightButtonPressed;
         Rotation = Math.Atan2(movementDirection.Y, movementDirection.X);
         Rotation = Math.Atan2(movementDirection.Y, movementDirection.X);
     }
     }
 }
 }

+ 16 - 0
src/PixiEditor.UI.Common/Controls/SelectableStrip.cs

@@ -2,6 +2,7 @@
 using Avalonia.Animation;
 using Avalonia.Animation;
 using Avalonia.Animation.Easings;
 using Avalonia.Animation.Easings;
 using Avalonia.Controls;
 using Avalonia.Controls;
+using Avalonia.Controls.Presenters;
 using Avalonia.Layout;
 using Avalonia.Layout;
 using Avalonia.Media;
 using Avalonia.Media;
 using Avalonia.Styling;
 using Avalonia.Styling;
@@ -86,6 +87,8 @@ public class SelectableStrip : Panel
         {
         {
             Children.Insert(0, _highlight);
             Children.Insert(0, _highlight);
         }
         }
+
+        FindSelectedItem();
     }
     }
 
 
     private static void OnSelectionChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
     private static void OnSelectionChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
@@ -110,6 +113,19 @@ public class SelectableStrip : Panel
         selectableStrip.HighlightX = pos.X;
         selectableStrip.HighlightX = pos.X;
     }
     }
 
 
+    private void FindSelectedItem()
+    {
+        foreach (var child in Children)
+        {
+            if (child is ContentPresenter presenter && presenter.Child != null && GetIsStripSelected(presenter.Child))
+            {
+                var pos = presenter.Child.TranslatePoint(new Point(0, 0), this) ?? new Point();
+                HighlightX = pos.X;
+                break;
+            }
+        }
+    }
+
     protected override Size ArrangeOverride(Size finalSize)
     protected override Size ArrangeOverride(Size finalSize)
     {
     {
         double x = 0;
         double x = 0;

BIN
src/PixiEditor/Data/BrushTools/Brightness.pixi


BIN
src/PixiEditor/Data/Brushes/PixelCircle.pixi


BIN
src/PixiEditor/Data/Brushes/PixelSquare.pixi


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

@@ -6,6 +6,7 @@
       "Icon": "icon-sun",
       "Icon": "icon-sun",
       "ToolTip": "BRIGHTNESS_TOOL_TOOLTIP",
       "ToolTip": "BRIGHTNESS_TOOL_TOOLTIP",
       "DefaultShortcut": "U",
       "DefaultShortcut": "U",
+      "SupportsSecondaryActionOnRightClick": true,
       "ActionDisplays": [
       "ActionDisplays": [
         {
         {
           "ActionDisplay": "BRIGHTNESS_TOOL_ACTION_DISPLAY_DEFAULT"
           "ActionDisplay": "BRIGHTNESS_TOOL_ACTION_DISPLAY_DEFAULT"
@@ -98,7 +99,8 @@
           "Settings": {
           "Settings": {
             "AntiAliasing": true,
             "AntiAliasing": true,
             "ExposeSpacing": true,
             "ExposeSpacing": true,
-            "PixelPerfectEnabled": false
+            "PixelPerfectEnabled": false,
+            "DefaultBrush": "Basic"
           }
           }
         },
         },
         "Select",
         "Select",

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

@@ -1288,5 +1288,7 @@
   "TARGET_BLEND_MODE": "Target",
   "TARGET_BLEND_MODE": "Target",
   "LAST_APPLIED_POINT": "Last Applied Point",
   "LAST_APPLIED_POINT": "Last Applied Point",
   "VIEWPORT_INFO_NODE": "Viewport Info",
   "VIEWPORT_INFO_NODE": "Viewport Info",
-  "EQUALS_NODE": "Equals"
+  "EQUALS_NODE": "Equals",
+  "IS_LEFT_BUTTON_PRESSED": "Is Left Button Pressed",
+  "IS_RIGHT_BUTTON_PRESSED": "Is Right Button Pressed"
 }
 }

+ 3 - 0
src/PixiEditor/Models/Config/ToolsConfig.cs

@@ -43,6 +43,7 @@ public class ToolsConfig : IMergeable<ToolsConfig>
                         existingTool.DefaultShortcut = string.IsNullOrEmpty(tool.DefaultShortcut)
                         existingTool.DefaultShortcut = string.IsNullOrEmpty(tool.DefaultShortcut)
                             ? existingTool.DefaultShortcut
                             ? existingTool.DefaultShortcut
                             : tool.DefaultShortcut;
                             : tool.DefaultShortcut;
+                        existingTool.SupportsSecondaryActionOnRightClick = tool.SupportsSecondaryActionOnRightClick;
                         existingTool.ToolTip = string.IsNullOrEmpty(tool.ToolTip) ? existingTool.ToolTip : tool.ToolTip;
                         existingTool.ToolTip = string.IsNullOrEmpty(tool.ToolTip) ? existingTool.ToolTip : tool.ToolTip;
                         if (existingTool.Settings != null && tool.Settings != null)
                         if (existingTool.Settings != null && tool.Settings != null)
                         {
                         {
@@ -92,6 +93,7 @@ public class ToolsConfig : IMergeable<ToolsConfig>
                                     : tool.DefaultShortcut;
                                     : tool.DefaultShortcut;
                                 existingTool.ToolTip =
                                 existingTool.ToolTip =
                                     string.IsNullOrEmpty(tool.ToolTip) ? existingTool.ToolTip : tool.ToolTip;
                                     string.IsNullOrEmpty(tool.ToolTip) ? existingTool.ToolTip : tool.ToolTip;
+                                existingTool.SupportsSecondaryActionOnRightClick = tool.SupportsSecondaryActionOnRightClick;
 
 
                                 if (existingTool.Settings != null && tool.Settings != null)
                                 if (existingTool.Settings != null && tool.Settings != null)
                                 {
                                 {
@@ -151,6 +153,7 @@ public class ToolConfig
     public bool IsSimpleTool => Settings == null || Settings.Count == 0;
     public bool IsSimpleTool => Settings == null || Settings.Count == 0;
     public string? Icon { get; set; }
     public string? Icon { get; set; }
     public List<ActionDisplayConfig>? ActionDisplays { get; set; }
     public List<ActionDisplayConfig>? ActionDisplays { get; set; }
+    public bool SupportsSecondaryActionOnRightClick { get; set; }
 }
 }
 
 
 public class ActionDisplayConfig
 public class ActionDisplayConfig

+ 1 - 0
src/PixiEditor/Models/Controllers/InputDevice/MouseOnCanvasEventArgs.cs

@@ -15,6 +15,7 @@ internal class MouseOnCanvasEventArgs : EventArgs
     public int ClickCount { get; set; } = 1;
     public int ClickCount { get; set; } = 1;
     public double ViewportScale { get; set; }
     public double ViewportScale { get; set; }
     public IReadOnlyList<PointerPosition> IntermediatePoints { get; set; }
     public IReadOnlyList<PointerPosition> IntermediatePoints { get; set; }
+    public PointerPointProperties Properties => Point.Properties;
 
 
     public MouseOnCanvasEventArgs(MouseButton button, PointerType type, VecD positionOnCanvas, KeyModifiers keyModifiers, int clickCount,
     public MouseOnCanvasEventArgs(MouseButton button, PointerType type, VecD positionOnCanvas, KeyModifiers keyModifiers, int clickCount,
         PointerPointProperties properties, double viewportScale)
         PointerPointProperties properties, double viewportScale)

+ 18 - 1
src/PixiEditor/Models/DocumentModels/ChangeExecutionController.cs

@@ -21,6 +21,7 @@ namespace PixiEditor.Models.DocumentModels;
 internal class ChangeExecutionController
 internal class ChangeExecutionController
 {
 {
     public bool LeftMousePressed { get; private set; }
     public bool LeftMousePressed { get; private set; }
+    public bool RightMousePressed { get; private set; }
     public ShapeCorners LastTransformState { get; private set; }
     public ShapeCorners LastTransformState { get; private set; }
     public VecI LastPixelPosition => lastPixelPos;
     public VecI LastPixelPosition => lastPixelPos;
     public VecD LastPrecisePosition => lastPrecisePos;
     public VecD LastPrecisePosition => lastPrecisePos;
@@ -240,7 +241,9 @@ internal class ChangeExecutionController
     {
     {
         //update internal state
         //update internal state
         LeftMousePressed = true;
         LeftMousePressed = true;
+        RightMousePressed = args.Properties.IsRightButtonPressed;
 
 
+        lastPointerInfo = ConstructPointerInfo(args.Point.PositionOnCanvas, args);
         if (_queuedExecutor != null && currentSession == null)
         if (_queuedExecutor != null && currentSession == null)
         {
         {
             StartExecutor(_queuedExecutor);
             StartExecutor(_queuedExecutor);
@@ -258,6 +261,20 @@ internal class ChangeExecutionController
         //call session events
         //call session events
         currentSession?.OnLeftMouseButtonUp(argsPositionOnCanvas);
         currentSession?.OnLeftMouseButtonUp(argsPositionOnCanvas);
     }
     }
+    
+    public void RightMouseButtonDownInlet(MouseOnCanvasEventArgs args)
+    {
+        RightMousePressed = true;
+        LeftMousePressed = args.Properties.IsLeftButtonPressed;
+        lastPointerInfo = ConstructPointerInfo(args.Point.PositionOnCanvas, args);
+        currentSession?.OnRightMouseButtonDown(args);
+    }
+    
+    public void RightMouseButtonUpInlet(VecD argsPositionOnCanvas)
+    {
+        RightMousePressed = false;
+        currentSession?.OnRightMouseButtonUp(argsPositionOnCanvas);
+    }
 
 
     public void TransformChangedInlet(ShapeCorners corners)
     public void TransformChangedInlet(ShapeCorners corners)
     {
     {
@@ -388,6 +405,6 @@ internal class ChangeExecutionController
         }
         }
 
 
         return new PointerInfo(currentPoint, pressure, args.Point.Properties.Twist,
         return new PointerInfo(currentPoint, pressure, args.Point.Properties.Twist,
-            new VecD(args.Point.Properties.XTilt, args.Point.Properties.YTilt), dirNormalized);
+            new VecD(args.Point.Properties.XTilt, args.Point.Properties.YTilt), dirNormalized, LeftMousePressed, RightMousePressed);
     }
     }
 }
 }

+ 2 - 0
src/PixiEditor/Models/DocumentModels/Public/DocumentEventsModule.cs

@@ -42,6 +42,8 @@ internal class DocumentEventsModule
     }
     }
 
 
     public void OnCanvasLeftMouseButtonUp(VecD argsPositionOnCanvas) => Internals.ChangeController.LeftMouseButtonUpInlet(argsPositionOnCanvas);
     public void OnCanvasLeftMouseButtonUp(VecD argsPositionOnCanvas) => Internals.ChangeController.LeftMouseButtonUpInlet(argsPositionOnCanvas);
+    public void OnCanvasRightMouseButtonDown(MouseOnCanvasEventArgs args) => Internals.ChangeController.RightMouseButtonDownInlet(args);
+    public void OnCanvasRightMouseButtonUp(VecD argsPositionOnCanvas) => Internals.ChangeController.RightMouseButtonUpInlet(argsPositionOnCanvas);
     public void OnOpacitySliderDragStarted() => Internals.ChangeController.OpacitySliderDragStartedInlet();
     public void OnOpacitySliderDragStarted() => Internals.ChangeController.OpacitySliderDragStartedInlet();
     public void OnOpacitySliderDragged(float newValue) => Internals.ChangeController.OpacitySliderDraggedInlet(newValue);
     public void OnOpacitySliderDragged(float newValue) => Internals.ChangeController.OpacitySliderDraggedInlet(newValue);
     public void OnOpacitySliderDragEnded() => Internals.ChangeController.OpacitySliderDragEndedInlet();
     public void OnOpacitySliderDragEnded() => Internals.ChangeController.OpacitySliderDragEndedInlet();

+ 6 - 0
src/PixiEditor/Models/DocumentModels/Public/DocumentOperationsModule.cs

@@ -248,6 +248,12 @@ internal class DocumentOperationsModule : IDocumentOperations
         return Internals.StructureHelper.CreateNewStructureMember(structureMemberType, name, source);
         return Internals.StructureHelper.CreateNewStructureMember(structureMemberType, name, source);
     }
     }
 
 
+    public Guid? ForceCreateStructureMember(Type structureMemberType, ActionSource source, string? name = null)
+    {
+        Internals.ChangeController.TryStopActiveExecutor();
+        return Internals.StructureHelper.CreateNewStructureMember(structureMemberType, name, source);
+    }
+
     /// <summary>
     /// <summary>
     /// Duplicates the member with the <paramref name="guidValue"/>
     /// Duplicates the member with the <paramref name="guidValue"/>
     /// </summary>
     /// </summary>

+ 2 - 2
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/BrushBasedExecutor.cs

@@ -138,7 +138,7 @@ internal class BrushBasedExecutor : UpdateableChangeExecutor
         return controller.LastPrecisePosition;
         return controller.LastPrecisePosition;
     }
     }
 
 
-    private VecD GetStabilizedPointTimeBased()
+    protected VecD GetStabilizedPointTimeBased()
     {
     {
         float timeConstant = (float)BrushToolbar.Stabilization / 100f;
         float timeConstant = (float)BrushToolbar.Stabilization / 100f;
         float elapsed = Math.Min((float)(DateTime.Now - lastTime).TotalSeconds, 0.1f);
         float elapsed = Math.Min((float)(DateTime.Now - lastTime).TotalSeconds, 0.1f);
@@ -150,7 +150,7 @@ internal class BrushBasedExecutor : UpdateableChangeExecutor
         return smoothed;
         return smoothed;
     }
     }
 
 
-    private VecD GetStabilizedPointCircleRope(double viewportZoom)
+    protected VecD GetStabilizedPointCircleRope(double viewportZoom)
     {
     {
         float radius = (float)BrushToolbar.Stabilization / (float)viewportZoom;
         float radius = (float)BrushToolbar.Stabilization / (float)viewportZoom;
         VecD direction = controller.LastPrecisePosition - lastSmoothed;
         VecD direction = controller.LastPrecisePosition - lastSmoothed;

+ 8 - 1
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/EraserToolExecutor.cs

@@ -20,9 +20,16 @@ internal class EraserToolExecutor : BrushBasedExecutor<IEraserToolHandler>
 {
 {
     protected override void EnqueueDrawActions()
     protected override void EnqueueDrawActions()
     {
     {
+        var point = GetStabilizedPoint();
+
+        if (handler != null)
+        {
+            handler.LastAppliedPoint = point;
+        }
+
         Color primaryColor = controller.EditorData.PrimaryColor.WithAlpha(0);
         Color primaryColor = controller.EditorData.PrimaryColor.WithAlpha(0);
         EditorData data = new EditorData(primaryColor, controller.EditorData.SecondaryColor);
         EditorData data = new EditorData(primaryColor, controller.EditorData.SecondaryColor);
-        var action = new LineBasedPen_Action(layerId, controller.LastPixelPosition, (float)ToolSize, antiAliasing,
+        var action = new LineBasedPen_Action(layerId, point, (float)ToolSize, antiAliasing,
             BrushData, drawOnMask,
             BrushData, drawOnMask,
             document!.AnimationHandler.ActiveFrameBindable, controller.LastPointerInfo, controller.LastKeyboardInfo, data);
             document!.AnimationHandler.ActiveFrameBindable, controller.LastPointerInfo, controller.LastKeyboardInfo, data);
 
 

+ 2 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/UpdateableChangeExecutor.cs

@@ -51,6 +51,8 @@ internal abstract class UpdateableChangeExecutor
     public virtual void OnPrecisePositionChange(MouseOnCanvasEventArgs args) { }
     public virtual void OnPrecisePositionChange(MouseOnCanvasEventArgs args) { }
     public virtual void OnLeftMouseButtonDown(MouseOnCanvasEventArgs args) { }
     public virtual void OnLeftMouseButtonDown(MouseOnCanvasEventArgs args) { }
     public virtual void OnLeftMouseButtonUp(VecD pos) { }
     public virtual void OnLeftMouseButtonUp(VecD pos) { }
+    public virtual void OnRightMouseButtonDown(MouseOnCanvasEventArgs args) { }
+    public virtual void OnRightMouseButtonUp(VecD pos) { }
     public virtual void OnOpacitySliderDragStarted() { }
     public virtual void OnOpacitySliderDragStarted() { }
     public virtual void OnOpacitySliderDragged(float newValue) { }
     public virtual void OnOpacitySliderDragged(float newValue) { }
     public virtual void OnOpacitySliderDragEnded() { }
     public virtual void OnOpacitySliderDragEnded() { }

+ 3 - 0
src/PixiEditor/Models/Handlers/IToolsHandler.cs

@@ -38,4 +38,7 @@ internal interface IToolsHandler : IHandler
     public void OnPreUndoInlet();
     public void OnPreUndoInlet();
     public void QuickToolSwitchInlet();
     public void QuickToolSwitchInlet();
     public void ChangeToolSize(double by);
     public void ChangeToolSize(double by);
+    public bool CreateLayerIfNeeded();
+    public bool NeedsNewLayerForActiveTool();
+    public void DeselectActiveTool();
 }
 }

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

@@ -47,7 +47,7 @@ internal class DocumentManagerViewModel : SubViewModel<ViewModelMain>, IDocument
             if (ViewModelMain.Current.ToolsSubViewModel.ActiveTool == null)
             if (ViewModelMain.Current.ToolsSubViewModel.ActiveTool == null)
             {
             {
                 var firstTool =
                 var firstTool =
-                    ViewModelMain.Current.ToolsSubViewModel.ActiveToolSet.Tools.FirstOrDefault(x =>
+                    ViewModelMain.Current.ToolsSubViewModel.ActiveToolSet?.Tools.FirstOrDefault(x =>
                         x.CanBeUsedOnActiveLayer);
                         x.CanBeUsedOnActiveLayer);
                 if (firstTool != null)
                 if (firstTool != null)
                 {
                 {

+ 22 - 1
src/PixiEditor/ViewModels/Document/DocumentViewModel.cs

@@ -231,6 +231,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
 
 
     private bool isDisposed = false;
     private bool isDisposed = false;
     private Guid referenceId = Guid.Empty;
     private Guid referenceId = Guid.Empty;
+    private Queue<Action> queuedLayerReadyToUseActions = new();
 
 
     private DocumentViewModel()
     private DocumentViewModel()
     {
     {
@@ -1056,6 +1057,9 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         {
         {
             // it might've been a better idea to implement this function asynchronously
             // it might've been a better idea to implement this function asynchronously
             // via a passthrough action to avoid all the try catches
             // via a passthrough action to avoid all the try catches
+            if (SizeBindable.X <= 0 || SizeBindable.Y <= 0)
+                return Colors.Transparent;
+
             if (scope == DocumentScope.Canvas)
             if (scope == DocumentScope.Canvas)
             {
             {
                 using Surface
                 using Surface
@@ -1096,7 +1100,19 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
 
 
 // these are intended to only be called from DocumentUpdater
 // these are intended to only be called from DocumentUpdater
 
 
-    public void InternalRaiseLayersChanged(LayersChangedEventArgs args) => LayersChanged?.Invoke(this, args);
+    public void InternalRaiseLayersChanged(LayersChangedEventArgs args)
+    {
+        LayersChanged?.Invoke(this, args);
+        if (queuedLayerReadyToUseActions.Count > 0)
+        {
+            foreach (var action in queuedLayerReadyToUseActions)
+            {
+                action();
+            }
+
+            queuedLayerReadyToUseActions.Clear();
+        }
+    }
 
 
     public void RaiseSizeChanged(DocumentSizeChangedEventArgs args) => SizeChanged?.Invoke(this, args);
     public void RaiseSizeChanged(DocumentSizeChangedEventArgs args) => SizeChanged?.Invoke(this, args);
 
 
@@ -1486,4 +1502,9 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
             nodeVm.UpdateLinkedStatus();
             nodeVm.UpdateLinkedStatus();
         }
         }
     }
     }
+
+    public void SubscribeLayerReadyToUseOnce(Action action)
+    {
+        queuedLayerReadyToUseActions.Enqueue(action);
+    }
 }
 }

+ 40 - 9
src/PixiEditor/ViewModels/SubViewModels/IoViewModel.cs

@@ -69,7 +69,7 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
 
 
         keyboardFilter.OnConvertedKeyDown += OnConvertedKeyDown;
         keyboardFilter.OnConvertedKeyDown += OnConvertedKeyDown;
         keyboardFilter.OnConvertedKeyUp += OnConvertedKeyUp;
         keyboardFilter.OnConvertedKeyUp += OnConvertedKeyUp;
-        
+
         Owner.AttachedToWindow += AttachWindowEvents;
         Owner.AttachedToWindow += AttachWindowEvents;
     }
     }
 
 
@@ -247,26 +247,50 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
         if (drawingWithRight != null || args.Button is not (MouseButton.Left or MouseButton.Right))
         if (drawingWithRight != null || args.Button is not (MouseButton.Left or MouseButton.Right))
             return;
             return;
 
 
-        if (args.Button == MouseButton.Right && !HandleRightMouseDown())
-            return;
-
         var docManager = Owner.DocumentManagerSubViewModel;
         var docManager = Owner.DocumentManagerSubViewModel;
         var activeDocument = docManager.ActiveDocument;
         var activeDocument = docManager.ActiveDocument;
         if (activeDocument == null)
         if (activeDocument == null)
             return;
             return;
 
 
+        if (args.Button == MouseButton.Right)
+        {
+            activeDocument.EventInlet.OnCanvasRightMouseButtonDown(args);
+            if (!HandleRightMouseDown())
+            {
+                return;
+            }
+        }
+
         drawingWithRight = args.Button == MouseButton.Right;
         drawingWithRight = args.Button == MouseButton.Right;
+
+
         activeDocument.EventInlet.OnCanvasLeftMouseButtonDown(args);
         activeDocument.EventInlet.OnCanvasLeftMouseButtonDown(args);
         if (args.Handled) return;
         if (args.Handled) return;
 
 
-        Owner.ToolsSubViewModel.UseToolEventInlet(args.Point.PositionOnCanvas, args.Button);
+        if (Owner.ToolsSubViewModel.NeedsNewLayerForActiveTool())
+        {
+            var activeToolType = Owner.ToolsSubViewModel.ActiveTool.GetType();
+            Owner.DocumentManagerSubViewModel.ActiveDocument.Tools.TryStopActiveTool();
+            Owner.ToolsSubViewModel.CreateLayerIfNeeded();
+            Owner.ToolsSubViewModel.DeselectActiveTool();
+            Owner.DocumentManagerSubViewModel.ActiveDocument.SubscribeLayerReadyToUseOnce(() =>
+            {
+                Owner.ToolsSubViewModel.SetActiveTool(activeToolType, false);
+                Owner.ToolsSubViewModel.UseToolEventInlet(args.Point.PositionOnCanvas, args.Button);
+            });
+        }
+        else
+        {
+            Owner.ToolsSubViewModel.UseToolEventInlet(args.Point.PositionOnCanvas, args.Button);
+        }
 
 
         if (args.Button == MouseButton.Right)
         if (args.Button == MouseButton.Right)
         {
         {
             HandleRightSwapColor();
             HandleRightSwapColor();
         }
         }
 
 
-        Analytics.SendUseTool(Owner.ToolsSubViewModel.ActiveTool, args.Point.PositionOnCanvas, activeDocument.SizeBindable);
+        Analytics.SendUseTool(Owner.ToolsSubViewModel.ActiveTool, args.Point.PositionOnCanvas,
+            activeDocument.SizeBindable);
     }
     }
 
 
     private bool HandleRightMouseDown()
     private bool HandleRightMouseDown()
@@ -296,10 +320,11 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
                 HandleRightMouseEraseDown(tools);
                 HandleRightMouseEraseDown(tools);
                 return true;
                 return true;
             }
             }
-            /*
-            case RightClickMode.SecondaryColor when tools.ActiveTool is BrightnessToolViewModel:
+            case RightClickMode.SecondaryColor when tools.ActiveTool is BrushBasedToolViewModel
+            {
+                SupportsSecondaryActionOnRightClick: true
+            }:
                 return true;
                 return true;
-            */
             case RightClickMode.ContextMenu:
             case RightClickMode.ContextMenu:
             default:
             default:
                 return false;
                 return false;
@@ -398,6 +423,12 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
                 .OnCanvasLeftMouseButtonUp(args.Point.PositionOnCanvas);
                 .OnCanvasLeftMouseButtonUp(args.Point.PositionOnCanvas);
         }
         }
 
 
+        if (button == MouseButton.Right)
+        {
+            Owner.DocumentManagerSubViewModel.ActiveDocument.EventInlet
+                .OnCanvasRightMouseButtonUp(args.Point.PositionOnCanvas);
+        }
+
         drawingWithRight = null;
         drawingWithRight = null;
 
 
         HandleRightMouseUp(button, tools);
         HandleRightMouseUp(button, tools);

+ 8 - 0
src/PixiEditor/ViewModels/SubViewModels/LayersViewModel.cs

@@ -171,6 +171,14 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         return doc.Operations.CreateStructureMember(layerType, source, name);
         return doc.Operations.CreateStructureMember(layerType, source, name);
     }
     }
 
 
+    public Guid? ForceNewLayer(Type layerType, ActionSource source, string? name = null)
+    {
+        if (Owner.DocumentManagerSubViewModel.ActiveDocument is not { } doc)
+            return null;
+
+        return doc.Operations.ForceCreateStructureMember(layerType, source, name);
+    }
+
     [Evaluator.CanExecute("PixiEditor.Layer.CanCreateNewMember")]
     [Evaluator.CanExecute("PixiEditor.Layer.CanCreateNewMember")]
     public bool CanCreateNewMember()
     public bool CanCreateNewMember()
     {
     {

+ 70 - 38
src/PixiEditor/ViewModels/SubViewModels/ToolsViewModel.cs

@@ -26,6 +26,7 @@ using PixiEditor.Helpers;
 using PixiEditor.Helpers.UI;
 using PixiEditor.Helpers.UI;
 using PixiEditor.Models.BrushEngine;
 using PixiEditor.Models.BrushEngine;
 using PixiEditor.Models.Commands;
 using PixiEditor.Models.Commands;
+using PixiEditor.Models.DocumentModels.Public;
 using PixiEditor.Models.Handlers.Toolbars;
 using PixiEditor.Models.Handlers.Toolbars;
 using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.Models.Input;
 using PixiEditor.Models.Input;
@@ -119,7 +120,7 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
         }
         }
     }
     }
 
 
-    public IToolSetHandler ActiveToolSet
+    public IToolSetHandler? ActiveToolSet
     {
     {
         get => _activeToolSet!;
         get => _activeToolSet!;
         private set => SetProperty(ref _activeToolSet, value);
         private set => SetProperty(ref _activeToolSet, value);
@@ -382,11 +383,13 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
         LastActionTool = ActiveTool;
         LastActionTool = ActiveTool;
         ActiveTool = tool;
         ActiveTool = tool;
 
 
-        ActiveTool.Toolbar.SettingChanged += ToolbarSettingChanged;
-
-        if (shareToolbar)
+        if (ActiveTool != null)
         {
         {
-            ActiveTool.Toolbar.LoadSharedSettings();
+            ActiveTool.Toolbar.SettingChanged += ToolbarSettingChanged;
+            if (shareToolbar)
+            {
+                ActiveTool.Toolbar.LoadSharedSettings();
+            }
         }
         }
 
 
         if (LastActionTool != ActiveTool)
         if (LastActionTool != ActiveTool)
@@ -394,15 +397,19 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
             SelectedToolChanged?.Invoke(this, new SelectedToolEventArgs(LastActionTool, ActiveTool));
             SelectedToolChanged?.Invoke(this, new SelectedToolEventArgs(LastActionTool, ActiveTool));
         }
         }
 
 
-        //update old tool
+
         LastActionTool?.KeyChanged(false, false, false, Key.None);
         LastActionTool?.KeyChanged(false, false, false, Key.None);
-        //update new tool
-        ActiveTool.KeyChanged(ctrlIsDown, shiftIsDown, altIsDown, lastKey);
-        ActiveTool.OnToolSelected(wasTransient);
 
 
-        tool.IsActive = true;
-        ActiveTool.IsTransient = transient;
-        SetToolCursor(tool.GetType());
+        ActiveTool?.KeyChanged(ctrlIsDown, shiftIsDown, altIsDown, lastKey);
+        ActiveTool?.OnToolSelected(wasTransient);
+
+        if (ActiveTool != null)
+        {
+            tool.IsActive = true;
+            ActiveTool.IsTransient = transient;
+            SetToolCursor(tool.GetType());
+        }
+
 
 
         if (Owner.StylusSubViewModel != null)
         if (Owner.StylusSubViewModel != null)
         {
         {
@@ -448,6 +455,41 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
             toolbar.ToolSize = newSize;
             toolbar.ToolSize = newSize;
     }
     }
 
 
+    public bool CreateLayerIfNeeded()
+    {
+        bool created = false;
+        if (NeedsNewLayerForActiveTool())
+        {
+            using var changeBlock = Owner.DocumentManagerSubViewModel.ActiveDocument.Operations.StartChangeBlock();
+            Guid? createdLayer = Owner.LayersSubViewModel.NewLayer(
+                ActiveTool.LayerTypeToCreateOnEmptyUse,
+                ActionSource.Automated,
+                ActiveTool.DefaultNewLayerName);
+            if (createdLayer is not null)
+            {
+                Owner.DocumentManagerSubViewModel.ActiveDocument.Operations.SetSelectedMember(createdLayer.Value);
+            }
+
+            changeBlock.ExecuteQueuedActions();
+            created = true;
+        }
+
+        return created;
+    }
+
+    public bool NeedsNewLayerForActiveTool()
+    {
+        return ActiveTool is not { CanBeUsedOnActiveLayer: true } && ActiveTool?.LayerTypeToCreateOnEmptyUse != null;
+    }
+
+    public void DeselectActiveTool()
+    {
+        if (ActiveTool != null)
+        {
+            SetActiveTool((IToolHandler)null, false, null);
+        }
+    }
+
     [Evaluator.CanExecute("PixiEditor.Tools.CanChangeToolSize",
     [Evaluator.CanExecute("PixiEditor.Tools.CanChangeToolSize",
         nameof(ActiveTool))]
         nameof(ActiveTool))]
     public bool CanChangeToolSize() => Owner.ToolsSubViewModel.ActiveTool?.Toolbar is IToolSizeToolbar
     public bool CanChangeToolSize() => Owner.ToolsSubViewModel.ActiveTool?.Toolbar is IToolSizeToolbar
@@ -574,31 +616,15 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
 
 
     public void UseToolEventInlet(VecD canvasPos, MouseButton button)
     public void UseToolEventInlet(VecD canvasPos, MouseButton button)
     {
     {
+        if (ActiveTool is null)
+            return;
+
         ActiveTool.UsedWith = button;
         ActiveTool.UsedWith = button;
         if (ActiveTool.StopsLinkedToolOnUse)
         if (ActiveTool.StopsLinkedToolOnUse)
         {
         {
             ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Operations.TryStopToolLinkedExecutor();
             ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Operations.TryStopToolLinkedExecutor();
         }
         }
 
 
-        bool waitForChange = false;
-
-        if (ActiveTool is not { CanBeUsedOnActiveLayer: true })
-        {
-            if (ActiveTool.LayerTypeToCreateOnEmptyUse == null) return;
-
-            using var changeBlock = Owner.DocumentManagerSubViewModel.ActiveDocument.Operations.StartChangeBlock();
-            Guid? createdLayer = Owner.LayersSubViewModel.NewLayer(
-                ActiveTool.LayerTypeToCreateOnEmptyUse,
-                ActionSource.Automated,
-                ActiveTool.DefaultNewLayerName);
-            if (createdLayer is not null)
-            {
-                Owner.DocumentManagerSubViewModel.ActiveDocument.Operations.SetSelectedMember(createdLayer.Value);
-            }
-
-            changeBlock.ExecuteQueuedActions();
-        }
-
         ActiveTool.UseTool(canvasPos);
         ActiveTool.UseTool(canvasPos);
     }
     }
 
 
@@ -727,8 +753,9 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
                 {
                 {
                     var brush = new Brush(uri, "TOOL_CONFIG");
                     var brush = new Brush(uri, "TOOL_CONFIG");
                     KeyCombination? shortcut = TryParseShortcut(toolFromToolset.DefaultShortcut);
                     KeyCombination? shortcut = TryParseShortcut(toolFromToolset.DefaultShortcut);
-                    return new BrushBasedToolViewModel(new BrushViewModel(brush), toolFromToolset.ToolTip, toolFromToolset.ToolName,
-                        shortcut, toolFromToolset.ActionDisplays);
+                    return new BrushBasedToolViewModel(new BrushViewModel(brush), toolFromToolset.ToolTip,
+                        toolFromToolset.ToolName,
+                        shortcut, toolFromToolset.ActionDisplays, toolFromToolset.SupportsSecondaryActionOnRightClick);
                 }
                 }
             }
             }
             catch
             catch
@@ -800,19 +827,24 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
     private void UpdateEnabledState()
     private void UpdateEnabledState()
     {
     {
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
         var doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
-        if (doc is null)
+        if (doc is null || ActiveToolSet is null)
             return;
             return;
 
 
         foreach (var toolHandler in ActiveToolSet.Tools)
         foreach (var toolHandler in ActiveToolSet.Tools)
         {
         {
             if (toolHandler is ToolViewModel tool)
             if (toolHandler is ToolViewModel tool)
             {
             {
-                List<IStructureMemberHandler> selectedLayers = new List<IStructureMemberHandler>
+                List<IStructureMemberHandler> selectedLayers = new List<IStructureMemberHandler>();
+                if (doc.SelectedStructureMember != null)
                 {
                 {
-                    doc.SelectedStructureMember
-                };
+                    selectedLayers.Add(doc.SelectedStructureMember);
+                }
+
+                if (doc.SoftSelectedStructureMembers != null)
+                {
+                    selectedLayers.AddRange(doc.SoftSelectedStructureMembers.Except(selectedLayers));
+                }
 
 
-                selectedLayers.AddRange(doc.SoftSelectedStructureMembers.Except(selectedLayers));
                 tool.SelectedLayersChanged(selectedLayers.ToArray());
                 tool.SelectedLayersChanged(selectedLayers.ToArray());
             }
             }
         }
         }

+ 18 - 16
src/PixiEditor/ViewModels/Tools/ToolSettings/Settings/Setting.cs

@@ -21,7 +21,7 @@ internal abstract class Setting<T> : Setting
         get
         get
         {
         {
             var adjusted = AdjustValue(base.Value);
             var adjusted = AdjustValue(base.Value);
-            if(adjusted != null && adjusted is not T)
+            if (adjusted != null && adjusted is not T)
             {
             {
                 return default;
                 return default;
             }
             }
@@ -51,13 +51,13 @@ internal abstract class Setting : ObservableObject
     private Dictionary<string, object> toolsetValues = new Dictionary<string, object>();
     private Dictionary<string, object> toolsetValues = new Dictionary<string, object>();
     private Dictionary<string, bool> defaultValuesSet = new Dictionary<string, bool>();
     private Dictionary<string, bool> defaultValuesSet = new Dictionary<string, bool>();
     private bool isExposed = true;
     private bool isExposed = true;
-    
+
     protected bool overwrittenExposed;
     protected bool overwrittenExposed;
     protected object overwrittenValue;
     protected object overwrittenValue;
 
 
     protected bool hasOverwrittenValue;
     protected bool hasOverwrittenValue;
     protected bool hasOverwrittenExposed;
     protected bool hasOverwrittenExposed;
-    
+
     protected Setting(string name)
     protected Setting(string name)
     {
     {
         Name = name;
         Name = name;
@@ -72,7 +72,7 @@ internal abstract class Setting : ObservableObject
         {
         {
             var old = toolsetValues.GetValueOrDefault(currentToolset, null);
             var old = toolsetValues.GetValueOrDefault(currentToolset, null);
 
 
-            if(value != null && old != null && value.GetType() != old.GetType())
+            if (value != null && old != null && value.GetType() != old.GetType())
             {
             {
                 try
                 try
                 {
                 {
@@ -127,15 +127,16 @@ internal abstract class Setting : ObservableObject
     }
     }
 
 
     public abstract Type GetSettingType();
     public abstract Type GetSettingType();
-    
+
     public void SetOverwriteValue(object value)
     public void SetOverwriteValue(object value)
     {
     {
         var adjusted = AdjustValue(value);
         var adjusted = AdjustValue(value);
         overwrittenValue = adjusted;
         overwrittenValue = adjusted;
         hasOverwrittenValue = true;
         hasOverwrittenValue = true;
-        
+
         OnPropertyChanged(nameof(Value));
         OnPropertyChanged(nameof(Value));
-        ValueChanged?.Invoke(this, new SettingValueChangedEventArgs<object>(toolsetValues.GetValueOrDefault(currentToolset, null), adjusted));
+        ValueChanged?.Invoke(this,
+            new SettingValueChangedEventArgs<object>(toolsetValues.GetValueOrDefault(currentToolset, null), adjusted));
     }
     }
 
 
     public void SetCurrentToolset(string toolset)
     public void SetCurrentToolset(string toolset)
@@ -150,37 +151,38 @@ internal abstract class Setting : ObservableObject
             }
             }
         }
         }
 
 
-        if(!toolsetValues.ContainsKey(currentToolset))
+        if (!toolsetValues.ContainsKey(currentToolset))
         {
         {
             toolsetValues[currentToolset] = toolsetValues.FirstOrDefault().Value;
             toolsetValues[currentToolset] = toolsetValues.FirstOrDefault().Value;
         }
         }
 
 
         OnPropertyChanged(nameof(Value));
         OnPropertyChanged(nameof(Value));
+        ValueChanged?.Invoke(this, new SettingValueChangedEventArgs<object>(null, Value));
     }
     }
 
 
     protected virtual object AdjustValue(object value)
     protected virtual object AdjustValue(object value)
     {
     {
         return value;
         return value;
     }
     }
-    
+
     public void SetOverwriteExposed(bool value)
     public void SetOverwriteExposed(bool value)
     {
     {
         overwrittenExposed = value;
         overwrittenExposed = value;
         hasOverwrittenExposed = true;
         hasOverwrittenExposed = true;
-        
+
         OnPropertyChanged(nameof(IsExposed));
         OnPropertyChanged(nameof(IsExposed));
     }
     }
 
 
-    public void SetDefaultValue(object defaultValue)
+    public void SetDefaultValue(object defaultValue, string toolset)
     {
     {
-        if (!defaultValuesSet.GetValueOrDefault(currentToolset, false))
+        if (!defaultValuesSet.GetValueOrDefault(toolset, false))
         {
         {
-            toolsetValues[currentToolset] = defaultValue;
-            defaultValuesSet[currentToolset] = true;
+            toolsetValues[toolset] = defaultValue;
+            defaultValuesSet[toolset] = true;
             OnPropertyChanged(nameof(Value));
             OnPropertyChanged(nameof(Value));
         }
         }
     }
     }
-    
+
     public void ResetOverwrite()
     public void ResetOverwrite()
     {
     {
         var old = overwrittenValue;
         var old = overwrittenValue;
@@ -189,7 +191,7 @@ internal abstract class Setting : ObservableObject
         overwrittenExposed = false;
         overwrittenExposed = false;
         hasOverwrittenValue = false;
         hasOverwrittenValue = false;
         hasOverwrittenExposed = false;
         hasOverwrittenExposed = false;
-        
+
         OnPropertyChanged(nameof(Value));
         OnPropertyChanged(nameof(Value));
         OnPropertyChanged(nameof(IsExposed));
         OnPropertyChanged(nameof(IsExposed));
 
 

+ 20 - 9
src/PixiEditor/ViewModels/Tools/ToolViewModel.cs

@@ -238,12 +238,30 @@ internal abstract class ToolViewModel : ObservableObject, IToolHandler
             IconOverwrite = icon;
             IconOverwrite = icon;
         }
         }
 
 
+        if (ToolSetSettings.TryGetValue(toolset, out var settings))
+        {
+            foreach (var setting in settings)
+            {
+                if (IsDefaultSetting(setting, out object defaultValue))
+                {
+                    string settingName = setting.Key.Replace("Default", string.Empty);
+                    var foundSetting = TryGetSettingByName(settingName, setting);
+                    if (foundSetting is null)
+                    {
+                        continue;
+                    }
+
+                    foundSetting.SetDefaultValue(defaultValue, toolset.Name);
+                }
+            }
+        }
+
         foreach (var toolbarSetting in toolbarSettings)
         foreach (var toolbarSetting in toolbarSettings)
         {
         {
             toolbarSetting.SetCurrentToolset(toolset.Name);
             toolbarSetting.SetCurrentToolset(toolset.Name);
         }
         }
 
 
-        if (!ToolSetSettings.TryGetValue(toolset, out var settings))
+        if (settings is null)
         {
         {
             return;
             return;
         }
         }
@@ -263,14 +281,7 @@ internal abstract class ToolViewModel : ObservableObject, IToolHandler
             }
             }
             else if (IsDefaultSetting(setting, out object defaultValue))
             else if (IsDefaultSetting(setting, out object defaultValue))
             {
             {
-                string settingName = setting.Key.Replace("Default", string.Empty);
-                var foundSetting = TryGetSettingByName(settingName, setting);
-                if (foundSetting is null)
-                {
-                    continue;
-                }
-
-                foundSetting.SetDefaultValue(defaultValue);
+                continue;
             }
             }
             else
             else
             {
             {

+ 6 - 2
src/PixiEditor/ViewModels/Tools/Tools/BrushBasedToolViewModel.cs

@@ -28,7 +28,10 @@ internal class BrushBasedToolViewModel : ToolViewModel, IBrushToolHandler
     public override string ToolNameLocalizationKey => toolName;
     public override string ToolNameLocalizationKey => toolName;
     public override string ToolName => toolName ?? base.ToolName;
     public override string ToolName => toolName ?? base.ToolName;
     public bool IsCustomBrushTool { get; private set; }
     public bool IsCustomBrushTool { get; private set; }
+    public override bool UsesColor => true;
+    public override bool IsErasable => true;
     public KeyCombination? DefaultShortcut { get; set; }
     public KeyCombination? DefaultShortcut { get; set; }
+    public bool SupportsSecondaryActionOnRightClick { get; set; }
 
 
     public VecD LastAppliedPoint
     public VecD LastAppliedPoint
     {
     {
@@ -52,7 +55,7 @@ internal class BrushBasedToolViewModel : ToolViewModel, IBrushToolHandler
     }
     }
 
 
     public BrushBasedToolViewModel(BrushViewModel brush, string? tooltip, string? toolName, KeyCombination? defaultShortcut,
     public BrushBasedToolViewModel(BrushViewModel brush, string? tooltip, string? toolName, KeyCombination? defaultShortcut,
-        List<ActionDisplayConfig>? actionDisplays)
+        List<ActionDisplayConfig>? actionDisplays, bool supportsSecondaryActionOnRightClick)
     {
     {
         Cursor = Cursors.PreciseCursor;
         Cursor = Cursors.PreciseCursor;
         Toolbar = CreateToolbar();
         Toolbar = CreateToolbar();
@@ -69,6 +72,7 @@ internal class BrushBasedToolViewModel : ToolViewModel, IBrushToolHandler
         DefaultShortcut = defaultShortcut;
         DefaultShortcut = defaultShortcut;
         IsCustomBrushTool = true;
         IsCustomBrushTool = true;
         this.actionDisplays = ParseActionDisplays(actionDisplays);
         this.actionDisplays = ParseActionDisplays(actionDisplays);
+        SupportsSecondaryActionOnRightClick = supportsSecondaryActionOnRightClick;
 
 
         if (this.actionDisplays is { Count: > 0 })
         if (this.actionDisplays is { Count: > 0 })
         {
         {
@@ -142,7 +146,7 @@ internal class BrushBasedToolViewModel : ToolViewModel, IBrushToolHandler
 
 
     private void OnSettingChanged(string name, object value)
     private void OnSettingChanged(string name, object value)
     {
     {
-        if (value is BrushViewModel)
+        if (value is BrushViewModel || name == nameof(BrushToolbar.Brush))
         {
         {
             AddBrushShapeSettings();
             AddBrushShapeSettings();
         }
         }

+ 22 - 9
src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml.cs

@@ -5,6 +5,7 @@ using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Input;
 using Avalonia.Input;
 using Avalonia.Interactivity;
 using Avalonia.Interactivity;
+using Avalonia.Media;
 using Avalonia.Skia;
 using Avalonia.Skia;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
 using ChunkyImageLib;
 using ChunkyImageLib;
@@ -383,7 +384,8 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         set => SetValue(ViewportRenderOutputProperty, value);
         set => SetValue(ViewportRenderOutputProperty, value);
     }
     }
 
 
-    public static readonly StyledProperty<Func<EditorData>> EditorDataFuncProperty = AvaloniaProperty.Register<Viewport, Func<EditorData>>("EditorDataFunc");
+    public static readonly StyledProperty<Func<EditorData>> EditorDataFuncProperty =
+        AvaloniaProperty.Register<Viewport, Func<EditorData>>("EditorDataFunc");
 
 
     public ObservableCollection<Overlay> ActiveOverlays { get; } = new();
     public ObservableCollection<Overlay> ActiveOverlays { get; } = new();
 
 
@@ -534,7 +536,8 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
     private ViewportInfo GetLocation()
     private ViewportInfo GetLocation()
     {
     {
         return new(AngleRadians, Center, RealDimensions,
         return new(AngleRadians, Center, RealDimensions,
-            new ViewportData(Scene.CalculateTransformMatrix().ToSKMatrix().ToMatrix3X3(), Scene.Pan, Scene.Scale, FlipX, FlipY),
+            new ViewportData(Scene.CalculateTransformMatrix().ToSKMatrix().ToMatrix3X3(), Scene.Pan, Scene.Scale, FlipX,
+                FlipY),
             Scene.LastPointerInfo,
             Scene.LastPointerInfo,
             Scene.LastKeyboardInfo,
             Scene.LastKeyboardInfo,
             EditorDataFunc(),
             EditorDataFunc(),
@@ -566,6 +569,8 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
 
 
         if (MouseDownCommand.CanExecute(parameter))
         if (MouseDownCommand.CanExecute(parameter))
             MouseDownCommand.Execute(parameter);
             MouseDownCommand.Execute(parameter);
+
+        e.Handled = true;
     }
     }
 
 
     private void Image_MouseMove(PointerEventArgs e)
     private void Image_MouseMove(PointerEventArgs e)
@@ -576,7 +581,8 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         VecD conv = Scene.ToZoomboxSpace(new VecD(pos.X, pos.Y));
         VecD conv = Scene.ToZoomboxSpace(new VecD(pos.X, pos.Y));
         MouseButton mouseButton = e.GetMouseButton(this);
         MouseButton mouseButton = e.GetMouseButton(this);
 
 
-        MouseOnCanvasEventArgs parameter = new(mouseButton, e.Pointer.Type, conv, e.KeyModifiers, 0, e.GetCurrentPoint(this).Properties, Scene.Scale);
+        MouseOnCanvasEventArgs parameter = new(mouseButton, e.Pointer.Type, conv, e.KeyModifiers, 0,
+            e.GetCurrentPoint(this).Properties, Scene.Scale);
 
 
         var intermediate = e.GetIntermediatePoints(this);
         var intermediate = e.GetIntermediatePoints(this);
         List<PointerPosition> intermediatePositions = new();
         List<PointerPosition> intermediatePositions = new();
@@ -592,6 +598,8 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
 
 
         if (MouseMoveCommand.CanExecute(parameter))
         if (MouseMoveCommand.CanExecute(parameter))
             MouseMoveCommand.Execute(parameter);
             MouseMoveCommand.Execute(parameter);
+
+        e.Handled = true;
     }
     }
 
 
     private void Image_MouseUp(object? sender, PointerReleasedEventArgs e)
     private void Image_MouseUp(object? sender, PointerReleasedEventArgs e)
@@ -601,9 +609,12 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
 
 
         Point pos = e.GetPosition(Scene);
         Point pos = e.GetPosition(Scene);
         VecD conv = Scene.ToZoomboxSpace(new VecD(pos.X, pos.Y));
         VecD conv = Scene.ToZoomboxSpace(new VecD(pos.X, pos.Y));
-        MouseOnCanvasEventArgs parameter = new(e.InitialPressMouseButton, e.Pointer.Type, conv, e.KeyModifiers, 0, e.GetCurrentPoint(this).Properties, Scene.Scale);
+        MouseOnCanvasEventArgs parameter = new(e.InitialPressMouseButton, e.Pointer.Type, conv, e.KeyModifiers, 0,
+            e.GetCurrentPoint(this).Properties, Scene.Scale);
         if (MouseUpCommand.CanExecute(parameter))
         if (MouseUpCommand.CanExecute(parameter))
             MouseUpCommand.Execute(parameter);
             MouseUpCommand.Execute(parameter);
+
+        //e.Handled = true;
     }
     }
 
 
     private void Image_MouseWheel(object? sender, PointerWheelEventArgs e)
     private void Image_MouseWheel(object? sender, PointerWheelEventArgs e)
@@ -725,14 +736,16 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         ViewportWindowViewModel vm = ((ViewportWindowViewModel)DataContext);
         ViewportWindowViewModel vm = ((ViewportWindowViewModel)DataContext);
         var tools = vm.Owner.Owner.ToolsSubViewModel;
         var tools = vm.Owner.Owner.ToolsSubViewModel;
 
 
-        /*
-        var superSpecialBrightnessTool = tools.RightClickMode == RightClickMode.SecondaryColor &&
-                                         tools.ActiveTool is BrightnessToolViewModel;
-        */
+        var superSpecialRightClick = tools is { RightClickMode: RightClickMode.SecondaryColor, ActiveTool: BrushBasedToolViewModel
+            {
+                SupportsSecondaryActionOnRightClick: true
+            }
+        };
+
         var superSpecialColorPicker =
         var superSpecialColorPicker =
             tools.RightClickMode == RightClickMode.Erase && tools.ActiveTool is ColorPickerToolViewModel;
             tools.RightClickMode == RightClickMode.Erase && tools.ActiveTool is ColorPickerToolViewModel;
 
 
-        if (/*superSpecialBrightnessTool || */superSpecialColorPicker)
+        if (superSpecialRightClick || superSpecialColorPicker)
         {
         {
             return;
             return;
         }
         }

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

@@ -183,7 +183,7 @@ internal class BrushShapeOverlay : Overlay
         VecD vecDir = new VecD(dir.X, dir.Y);
         VecD vecDir = new VecD(dir.X, dir.Y);
         VecD dirNormalized = vecDir.Length > 0 ? vecDir.Normalize() : lastPointerInfo.MovementDirection;
         VecD dirNormalized = vecDir.Length > 0 ? vecDir.Normalize() : lastPointerInfo.MovementDirection;
 
 
-        PointerInfo pointer = new PointerInfo(pos, 1, 0, VecD.Zero, dirNormalized);
+        PointerInfo pointer = new PointerInfo(pos, 1, 0, VecD.Zero, dirNormalized, true, false);
 
 
         engine.ExecuteBrush(null, BrushData, pos, ActiveFrameTime,
         engine.ExecuteBrush(null, BrushData, pos, ActiveFrameTime,
             ColorSpace.CreateSrgb(), SamplingOptions.Default, pointer, new KeyboardInfo(),
             ColorSpace.CreateSrgb(), SamplingOptions.Default, pointer, new KeyboardInfo(),

+ 1 - 1
src/PixiEditor/Views/Rendering/Scene.cs

@@ -819,7 +819,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         Point dir = lastDirCalculationPoint - data.Position;
         Point dir = lastDirCalculationPoint - data.Position;
         VecD vecDir = new VecD(dir.X, dir.Y);
         VecD vecDir = new VecD(dir.X, dir.Y);
         VecD dirNormalized = vecDir.Length > 0 ? vecDir.Normalize() : lastPointerInfo.MovementDirection;
         VecD dirNormalized = vecDir.Length > 0 ? vecDir.Normalize() : lastPointerInfo.MovementDirection;
-        return new PointerInfo(position, pressure, properties.Twist, new VecD(properties.XTilt, properties.YTilt), dirNormalized);
+        return new PointerInfo(position, pressure, properties.Twist, new VecD(properties.XTilt, properties.YTilt), dirNormalized, e.Properties.IsLeftButtonPressed, e.Properties.IsRightButtonPressed);
     }
     }
 
 
     private static Point Lerp(VecD a, VecD b, float t)
     private static Point Lerp(VecD a, VecD b, float t)

+ 1 - 1
src/PixiEditor/Views/Tools/ToolSettings/Settings/EnumSettingView.axaml

@@ -19,7 +19,7 @@
         <ComboBox VerticalAlignment="Center"
         <ComboBox VerticalAlignment="Center"
                   MinWidth="85"
                   MinWidth="85"
                   IsVisible="{Binding !PickerIsIconButtons}"
                   IsVisible="{Binding !PickerIsIconButtons}"
-                  SelectedIndex="{Binding Value, Mode=TwoWay}"
+                  SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"
                   ItemsSource="{Binding EnumValues}">
                   ItemsSource="{Binding EnumValues}">
             <ComboBox.ItemContainerTheme>
             <ComboBox.ItemContainerTheme>
                 <ControlTheme TargetType="ComboBoxItem" BasedOn="{StaticResource {x:Type ComboBoxItem}}">
                 <ControlTheme TargetType="ComboBoxItem" BasedOn="{StaticResource {x:Type ComboBoxItem}}">