Browse Source

GradientNode and EditorInfoNode

Krzysztof Krysiński 2 tuần trước cách đây
mục cha
commit
d36bbd6e5d
25 tập tin đã thay đổi với 300 bổ sung32 xóa
  1. 1 1
      src/Drawie
  2. 5 4
      src/PixiEditor.ChangeableDocument/Changeables/Brushes/BrushEngine.cs
  3. 28 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Editor/EditorInfoNode.cs
  4. 136 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/GradientNode.cs
  5. 6 4
      src/PixiEditor.ChangeableDocument/Changes/Drawing/LineBasedPen_UpdateableChange.cs
  6. 2 0
      src/PixiEditor.ChangeableDocument/Changes/NodeGraph/ConversionTable.cs
  7. 15 0
      src/PixiEditor.ChangeableDocument/Rendering/ContextData/EditorData.cs
  8. 1 1
      src/PixiEditor.ChangeableDocument/Rendering/RenderContext.cs
  9. 4 1
      src/PixiEditor.Extensions/UI/Overlays/IOverlayPointer.cs
  10. 3 3
      src/PixiEditor/Models/Controllers/InputDevice/MouseInputFilter.cs
  11. 3 1
      src/PixiEditor/Models/Controllers/InputDevice/MouseOnCanvasEventArgs.cs
  12. 18 4
      src/PixiEditor/Models/DocumentModels/ChangeExecutionController.cs
  13. 2 2
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/EraserToolExecutor.cs
  14. 3 3
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/PenToolExecutor.cs
  15. 4 0
      src/PixiEditor/Models/EnumTranslations.cs
  16. 4 2
      src/PixiEditor/Models/Rendering/SceneRenderer.cs
  17. 10 0
      src/PixiEditor/ViewModels/Document/Nodes/Editor/EditorInfoNodeViewModel.cs
  18. 10 0
      src/PixiEditor/ViewModels/Document/Nodes/GradientNodeViewModel.cs
  19. 10 0
      src/PixiEditor/ViewModels/ViewModelMain.cs
  20. 1 0
      src/PixiEditor/Views/Dock/DocumentTemplate.axaml
  21. 1 0
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml
  22. 12 3
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml.cs
  23. 3 0
      src/PixiEditor/Views/Overlays/Pointers/MouseOverlayPointer.cs
  24. 1 1
      src/PixiEditor/Views/Overlays/TransformOverlay/TransformOverlay.cs
  25. 17 2
      src/PixiEditor/Views/Rendering/Scene.cs

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit bd525be43b3c9cdf40eea60bb8ac1f3d9e3142dd
+Subproject commit 3c06f52bee39798848ee1f9274a0c68c496812b6

+ 5 - 4
src/PixiEditor.ChangeableDocument/Changeables/Brushes/BrushEngine.cs

@@ -1,4 +1,5 @@
 using Drawie.Backend.Core;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
@@ -16,14 +17,14 @@ namespace PixiEditor.ChangeableDocument.Changeables.Brushes;
 
 internal class BrushEngine
 {
-    public void ExecuteBrush(ChunkyImage target, BrushData brushData, VecI point, KeyFrameTime frameTime, ColorSpace cs, SamplingOptions samplingOptions, PointerInfo pointerInfo)
+    public void ExecuteBrush(ChunkyImage target, BrushData brushData, VecI point, KeyFrameTime frameTime, ColorSpace cs, SamplingOptions samplingOptions, PointerInfo pointerInfo, EditorData editorData)
     {
-        ExecuteVectorShapeBrush(target, brushData, point, frameTime, cs, samplingOptions, pointerInfo);
+        ExecuteVectorShapeBrush(target, brushData, point, frameTime, cs, samplingOptions, pointerInfo, editorData);
     }
 
     private void ExecuteVectorShapeBrush(ChunkyImage target, BrushData brushData, VecI point, KeyFrameTime frameTime,
         ColorSpace colorSpace, SamplingOptions samplingOptions,
-        PointerInfo pointerInfo)
+        PointerInfo pointerInfo, EditorData editorData)
     {
         var brushNode = brushData.BrushGraph.AllNodes.FirstOrDefault(x => x is BrushOutputNode) as BrushOutputNode;
         if (brushNode == null)
@@ -34,7 +35,7 @@ internal class BrushEngine
         VecI size = new VecI((int)float.Ceiling(brushData.StrokeWidth));
         using var texture = Texture.ForDisplay(size);
         RenderContext context = new RenderContext(texture.DrawingSurface, frameTime, ChunkResolution.Full, size, size,
-            colorSpace, samplingOptions) { PointerInfo = pointerInfo };
+            colorSpace, samplingOptions) { PointerInfo = pointerInfo, EditorData = editorData };
 
         brushData.BrushGraph.Execute(brushNode, context);
 

+ 28 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Editor/EditorInfoNode.cs

@@ -0,0 +1,28 @@
+using Drawie.Backend.Core.ColorsImpl;
+using PixiEditor.ChangeableDocument.Rendering;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Editor;
+
+[NodeInfo("EditorInfo")]
+public class EditorInfoNode : Node
+{
+    public OutputProperty<Color> PrimaryColor { get; }
+    public OutputProperty<Color> SecondaryColor { get; }
+
+    public EditorInfoNode()
+    {
+        PrimaryColor = CreateOutput<Color>("PrimaryColor", "PRIMARY_COLOR", Colors.Black);
+        SecondaryColor = CreateOutput<Color>("SecondaryColor", "SECONDARY_COLOR", Colors.White);
+    }
+
+    protected override void OnExecute(RenderContext context)
+    {
+        PrimaryColor.Value = context.EditorData.PrimaryColor;
+        SecondaryColor.Value = context.EditorData.SecondaryColor;
+    }
+
+    public override Node CreateCopy()
+    {
+        return new EditorInfoNode();
+    }
+}

+ 136 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/GradientNode.cs

@@ -0,0 +1,136 @@
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
+using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Rendering;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+
+[NodeInfo("Gradient")]
+public class GradientNode : Node
+{
+    public InputProperty<GradientType> Type { get; }
+    public InputProperty<VecD> StartPoint { get; private set; }
+    public InputProperty<VecD> EndPoint { get; private set; }
+    public InputProperty<VecD> CenterPoint { get; private set; }
+    public InputProperty<double> Radius { get; private set; }
+    public InputProperty<double> Angle { get; private set; }
+    public InputProperty<int> StopsCount { get; }
+    public OutputProperty<GradientPaintable> Gradient { get; }
+
+    public Dictionary<InputProperty<Color>, InputProperty<float>> ColorStops { get; } = new();
+
+    public GradientNode()
+    {
+        Gradient = CreateOutput<GradientPaintable>("Gradient", "GRADIENT", null);
+        Type = CreateInput<GradientType>("Type", "TYPE", GradientType.Linear)
+            .NonOverridenChanged(UpdateType);
+        StartPoint = CreateInput<VecD>("StartPoint", "START_POINT", new VecD(0, 0));
+        EndPoint = CreateInput<VecD>("EndPoint", "END_POINT", new VecD(1, 0));
+        StopsCount = CreateInput<int>("StopsCount", "STOPS_COUNT", 2)
+            .NonOverridenChanged(_ => RegenerateStops());
+
+        GenerateStops();
+    }
+
+    private void UpdateType(GradientType type)
+    {
+        if (type == GradientType.Linear)
+        {
+            RemoveInputProperty(CenterPoint);
+            RemoveInputProperty(Radius);
+            RemoveInputProperty(Angle);
+            if (!HasInputProperty(StartPoint.InternalPropertyName))
+            {
+                StartPoint = CreateInput<VecD>("StartPoint", "START_POINT", new VecD(0, 0));
+            }
+
+            if (!HasInputProperty(EndPoint.InternalPropertyName))
+            {
+                EndPoint = CreateInput<VecD>("EndPoint", "END_POINT", new VecD(1, 0));
+            }
+        }
+        else if (type == GradientType.Radial)
+        {
+            RemoveInputProperty(StartPoint);
+            RemoveInputProperty(EndPoint);
+            RemoveInputProperty(Angle);
+            if (!HasInputProperty("CenterPoint"))
+            {
+                CenterPoint = CreateInput<VecD>("CenterPoint", "CENTER_POINT", new VecD(0.5, 0.5));
+            }
+
+            if (!HasInputProperty("Radius"))
+            {
+                Radius = CreateInput<double>("Radius", "RADIUS", 0.5);
+            }
+        }
+        else if (type == GradientType.Conical)
+        {
+            RemoveInputProperty(StartPoint);
+            RemoveInputProperty(EndPoint);
+            RemoveInputProperty(Radius);
+            if (!HasInputProperty("CenterPoint"))
+            {
+                CenterPoint = CreateInput<VecD>("CenterPoint", "CENTER_POINT", new VecD(0.5, 0.5));
+            }
+
+            if (!HasInputProperty("Angle"))
+            {
+                Angle = CreateInput<double>("Angle", "ANGLE", 0);
+            }
+        }
+    }
+
+    private void RegenerateStops()
+    {
+        foreach (var (colorInput, positionInput) in ColorStops)
+        {
+            RemoveInputProperty(colorInput);
+            RemoveInputProperty(positionInput);
+        }
+
+        ColorStops.Clear();
+        GenerateStops();
+    }
+
+    private void GenerateStops()
+    {
+        for (int i = 0; i < StopsCount.Value; i++)
+        {
+            var colorInput = CreateInput<Color>($"ColorStop{i + 1}Color", $"COLOR_STOP_COLOR",
+                Drawie.Backend.Core.ColorsImpl.Colors.White);
+            var positionInput = CreateInput<float>($"ColorStop{i + 1}Position", $"COLOR_STOP_POSITION",
+                i / (float)(StopsCount.Value - 1));
+            ColorStops[colorInput] = positionInput;
+        }
+    }
+
+    protected override void OnExecute(RenderContext context)
+    {
+        var stops = ColorStops.Select(kvp => new GradientStop(kvp.Key.Value, kvp.Value.Value)).ToList();
+        Gradient.Value = GenerateGradient(Type.Value, stops);
+    }
+
+    private GradientPaintable GenerateGradient(GradientType type, List<GradientStop> stops)
+    {
+        return type switch
+        {
+            GradientType.Linear => new LinearGradientPaintable(StartPoint.Value, EndPoint.Value, stops),
+            GradientType.Radial => new RadialGradientPaintable(CenterPoint.Value, Radius.Value, stops),
+            GradientType.Conical => new SweepGradientPaintable(CenterPoint.Value, Angle.Value, stops),
+            _ => throw new NotImplementedException("Unknown gradient type")
+        };
+    }
+
+    public override Node CreateCopy()
+    {
+        return new GradientNode();
+    }
+}
+
+public enum GradientType
+{
+    Linear,
+    Radial,
+    Conical
+}

+ 6 - 4
src/PixiEditor.ChangeableDocument/Changes/Drawing/LineBasedPen_UpdateableChange.cs

@@ -42,6 +42,7 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
     private int lastAppliedPointIndex = -1;
     private BrushOutputNode? brushOutputNode;
     private PointerInfo pointerInfo;
+    private EditorData editorData;
 
     [GenerateUpdateableChangeActions]
     public LineBasedPen_UpdateableChange(Guid memberGuid, Color color, VecI pos, float strokeWidth, bool erasing,
@@ -49,7 +50,7 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
         float hardness,
         float spacing,
         Guid brushOutputGuid,
-        bool drawOnMask, int frame, PointerInfo pointerInfo)
+        bool drawOnMask, int frame, PointerInfo pointerInfo, EditorData editorData)
     {
         this.memberGuid = memberGuid;
         this.color = color;
@@ -63,6 +64,7 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
         points.Add(pos);
         this.frame = frame;
         this.pointerInfo = pointerInfo;
+        this.editorData = editorData;
 
         srcPaint.Shader?.Dispose();
         srcPaint.Shader = null;
@@ -153,7 +155,7 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
             brushData.StrokeWidth = strokeWidth;
 
             engine.ExecuteBrush(image, brushData, point, frame, target.ProcessingColorSpace, SamplingOptions.Default,
-                pointerInfo);
+                pointerInfo, editorData);
 
             /*if (brushData.VectorShape == null)
             {
@@ -213,7 +215,7 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
             finalPaintable = color;
 
             engine.ExecuteBrush(targetImage, brushData, points[0], frameTime, targetImage.ProcessingColorSpace,
-                SamplingOptions.Default, pointerInfo);
+                SamplingOptions.Default, pointerInfo, editorData);
 
             return;
         }
@@ -232,7 +234,7 @@ internal class LineBasedPen_UpdateableChange : UpdateableChange
             finalPaintable = color;
 
             engine.ExecuteBrush(targetImage, brushData, points[i], frameTime, targetImage.ProcessingColorSpace,
-                SamplingOptions.Default, pointerInfo);
+                SamplingOptions.Default, pointerInfo, editorData);
         }
     }
 

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

@@ -7,6 +7,8 @@ using PixiEditor.ChangeableDocument.Changeables.Graph.Context;
 using Drawie.Backend.Core.Shaders.Generation;
 using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Graph;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 namespace PixiEditor.ChangeableDocument.Changes.NodeGraph;
 

+ 15 - 0
src/PixiEditor.ChangeableDocument/Rendering/ContextData/EditorData.cs

@@ -0,0 +1,15 @@
+using Drawie.Backend.Core.ColorsImpl;
+
+namespace PixiEditor.ChangeableDocument.Rendering.ContextData;
+
+public struct EditorData
+{
+    public Color PrimaryColor { get; }
+    public Color SecondaryColor { get; }
+
+    public EditorData(Color primaryColor, Color secondaryColor)
+    {
+        PrimaryColor = primaryColor;
+        SecondaryColor = secondaryColor;
+    }
+}

+ 1 - 1
src/PixiEditor.ChangeableDocument/Rendering/RenderContext.cs

@@ -20,8 +20,8 @@ public class RenderContext
     public VecI DocumentSize { get; set; }
     public DrawingSurface RenderSurface { get; set; }
     public bool FullRerender { get; set; } = false;
-
     public PointerInfo PointerInfo { get; set; }
+    public EditorData EditorData { get; set; }
     public ColorSpace ProcessingColorSpace { get; set; }
     public string? TargetOutput { get; set; }
 

+ 4 - 1
src/PixiEditor.Extensions/UI/Overlays/IOverlayPointer.cs

@@ -1,6 +1,9 @@
-namespace PixiEditor.Extensions.UI.Overlays;
+using Avalonia.Input;
+
+namespace PixiEditor.Extensions.UI.Overlays;
 
 public interface IOverlayPointer
 {
+    public PointerType Type { get; }
     public void Capture(IOverlay? overlay);
 }

+ 3 - 3
src/PixiEditor/Models/Controllers/InputDevice/MouseInputFilter.cs

@@ -53,13 +53,13 @@ internal class MouseInputFilter
 
     public void DeactivatedInlet(object? sender, EventArgs e)
     {
-        MouseOnCanvasEventArgs argsLeft = new(MouseButton.Left, VecD.Zero, KeyModifiers.None, 0, PointerPointProperties.None);
+        MouseOnCanvasEventArgs argsLeft = new(MouseButton.Left, PointerType.Mouse, VecD.Zero, KeyModifiers.None, 0, PointerPointProperties.None);
         MouseUpInlet(argsLeft);
         
-        MouseOnCanvasEventArgs argsMiddle = new(MouseButton.Middle, VecD.Zero, KeyModifiers.None, 0, PointerPointProperties.None);
+        MouseOnCanvasEventArgs argsMiddle = new(MouseButton.Middle, PointerType.Mouse, VecD.Zero, KeyModifiers.None, 0, PointerPointProperties.None);
         MouseUpInlet(argsMiddle);
         
-        MouseOnCanvasEventArgs argsRight = new(MouseButton.Right, VecD.Zero, KeyModifiers.None, 0, PointerPointProperties.None);
+        MouseOnCanvasEventArgs argsRight = new(MouseButton.Right, PointerType.Mouse, VecD.Zero, KeyModifiers.None, 0, PointerPointProperties.None);
         MouseUpInlet(argsRight);
     }
 }

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

@@ -7,13 +7,14 @@ namespace PixiEditor.Models.Controllers.InputDevice;
 internal class MouseOnCanvasEventArgs : EventArgs
 {
     public MouseButton Button { get; }
+    public PointerType PointerType { get; }
     public VecD PositionOnCanvas { get; }
     public KeyModifiers KeyModifiers { get; }
     public bool Handled { get; set; }
     public int ClickCount { get; set; } = 1;
     public PointerPointProperties Properties { get; }
 
-    public MouseOnCanvasEventArgs(MouseButton button, VecD positionOnCanvas, KeyModifiers keyModifiers, int clickCount,
+    public MouseOnCanvasEventArgs(MouseButton button, PointerType type, VecD positionOnCanvas, KeyModifiers keyModifiers, int clickCount,
         PointerPointProperties properties)
     {
         Button = button;
@@ -21,5 +22,6 @@ internal class MouseOnCanvasEventArgs : EventArgs
         KeyModifiers = keyModifiers;
         ClickCount = clickCount;
         Properties = properties;
+        PointerType = type;
     }
 }

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

@@ -11,6 +11,7 @@ using PixiEditor.Models.Tools;
 using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Rendering.ContextData;
 using PixiEditor.Models.Controllers.InputDevice;
+using PixiEditor.ViewModels;
 using PixiEditor.Views.Overlays.SymmetryOverlay;
 
 namespace PixiEditor.Models.DocumentModels;
@@ -23,6 +24,7 @@ internal class ChangeExecutionController
     public VecD LastPrecisePosition => lastPrecisePos;
     public PointerInfo LastPointerInfo => lastPointerInfo;
     public bool IsBlockingChangeActive => currentSession is not null && currentSession.BlocksOtherActions;
+    public EditorData EditorData => GetEditorData();
 
     public event Action ToolSessionFinished;
 
@@ -35,6 +37,7 @@ internal class ChangeExecutionController
     private PointerInfo pointerInfo;
     private VecD lastDirCalculationPoint;
     private PointerInfo lastPointerInfo;
+    private ViewModelMain viewModelMain;
 
     private UpdateableChangeExecutor? currentSession = null;
 
@@ -45,6 +48,7 @@ internal class ChangeExecutionController
         this.document = document;
         this.internals = internals;
         this.services = services;
+        viewModelMain = (ViewModelMain)services.GetService(typeof(ViewModelMain))!;
     }
 
     public bool IsChangeOfTypeActive<T>() where T : IExecutorFeature
@@ -177,7 +181,7 @@ internal class ChangeExecutionController
 
         lastPrecisePos = newCanvasPos;
 
-        lastPointerInfo = ConstructPointerInfo(newCanvasPos, args.Properties);
+        lastPointerInfo = ConstructPointerInfo(newCanvasPos, args);
 
         //call session events
         if (currentSession is not null)
@@ -330,18 +334,28 @@ internal class ChangeExecutionController
         }
     }
 
+    private EditorData GetEditorData()
+    {
+        return viewModelMain.GetEditorData();
+    }
 
-    private PointerInfo ConstructPointerInfo(VecD currentPoint, PointerPointProperties properties)
+    private PointerInfo ConstructPointerInfo(VecD currentPoint, MouseOnCanvasEventArgs args)
     {
         if (VecD.Distance(lastDirCalculationPoint, currentPoint) > 20)
         {
             lastDirCalculationPoint = lastDirCalculationPoint.Lerp(currentPoint, 0.5f);
         }
 
+        float pressure = args.Properties.Pressure;
+        if (args.PointerType == PointerType.Mouse)
+        {
+            pressure = args.Properties.Pressure > 0 ? 1 : 0;
+        }
+
         VecD dir = lastDirCalculationPoint - currentPoint;
         VecD vecDir = new VecD(dir.X, dir.Y);
         VecD dirNormalized = vecDir.Length > 0 ? vecDir.Normalize() : lastPointerInfo.MovementDirection;
-        return new PointerInfo(currentPoint, properties.Pressure, properties.Twist,
-            new VecD(properties.XTilt, properties.YTilt), dirNormalized);
+        return new PointerInfo(currentPoint, pressure, args.Properties.Twist,
+            new VecD(args.Properties.XTilt, args.Properties.YTilt), dirNormalized);
     }
 }

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

@@ -52,7 +52,7 @@ internal class EraserToolExecutor : UpdateableChangeExecutor
 
         colorsHandler.AddSwatch(new PaletteColor(color.R, color.G, color.B));
         IAction? action = new LineBasedPen_Action(guidValue, Colors.White, controller!.LastPixelPosition, (float)eraserTool.ToolSize, true,
-            antiAliasing, hardness, spacing, Guid.Empty, drawOnMask, document!.AnimationHandler.ActiveFrameBindable, controller.LastPointerInfo);
+            antiAliasing, hardness, spacing, Guid.Empty, drawOnMask, document!.AnimationHandler.ActiveFrameBindable, controller.LastPointerInfo, controller.EditorData);
         internals!.ActionAccumulator.AddActions(action);
 
         return ExecutionState.Success;
@@ -60,7 +60,7 @@ internal class EraserToolExecutor : UpdateableChangeExecutor
 
     public override void OnPixelPositionChange(VecI pos, MouseOnCanvasEventArgs args)
     {
-        IAction? action = new LineBasedPen_Action(guidValue, Colors.White, pos, (float)toolSize, true, antiAliasing, hardness, spacing, Guid.Empty, drawOnMask, document!.AnimationHandler.ActiveFrameBindable, controller.LastPointerInfo);
+        IAction? action = new LineBasedPen_Action(guidValue, Colors.White, pos, (float)toolSize, true, antiAliasing, hardness, spacing, Guid.Empty, drawOnMask, document!.AnimationHandler.ActiveFrameBindable, controller.LastPointerInfo, controller.EditorData);
         internals!.ActionAccumulator.AddActions(action);
     }
 

+ 3 - 3
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/PenToolExecutor.cs

@@ -87,7 +87,7 @@ internal class PenToolExecutor : UpdateableChangeExecutor
             {
                 false => new LineBasedPen_Action(guidValue, color, controller!.LastPixelPosition, (float)ToolSize,
                     transparentErase, antiAliasing, hardness, spacing, brushOutputGuid, drawOnMask,
-                    document!.AnimationHandler.ActiveFrameBindable, controller.LastPointerInfo),
+                    document!.AnimationHandler.ActiveFrameBindable, controller.LastPointerInfo, controller.EditorData),
                 true => new PixelPerfectPen_Action(guidValue, controller!.LastPixelPosition, color, drawOnMask,
                     document!.AnimationHandler.ActiveFrameBindable)
             };
@@ -106,7 +106,7 @@ internal class PenToolExecutor : UpdateableChangeExecutor
         {
             false => new LineBasedPen_Action(guidValue, color, controller!.LastPixelPosition, (float)ToolSize,
                 transparentErase, antiAliasing, hardness, spacing, brushOutputGuid, drawOnMask,
-                document!.AnimationHandler.ActiveFrameBindable, controller.LastPointerInfo),
+                document!.AnimationHandler.ActiveFrameBindable, controller.LastPointerInfo, controller.EditorData),
             true => new PixelPerfectPen_Action(guidValue, controller!.LastPixelPosition, color, drawOnMask,
                 document!.AnimationHandler.ActiveFrameBindable)
         };
@@ -132,7 +132,7 @@ internal class PenToolExecutor : UpdateableChangeExecutor
             {
                 false => new LineBasedPen_Action(guidValue, color, pos, (float)ToolSize, transparentErase, antiAliasing,
                     hardness, spacing, brushOutputGuid, drawOnMask, document!.AnimationHandler.ActiveFrameBindable,
-                    controller.LastPointerInfo),
+                    controller.LastPointerInfo, controller.EditorData),
                 true => new PixelPerfectPen_Action(guidValue, pos, color, drawOnMask,
                     document!.AnimationHandler.ActiveFrameBindable)
             };

+ 4 - 0
src/PixiEditor/Models/EnumTranslations.cs

@@ -118,3 +118,7 @@ using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
 [assembly: LocalizeEnum<BlendMode>(BlendMode.Exclusion, "EXCLUSION_BLEND_MODE")]
 [assembly: LocalizeEnum<BlendMode>(BlendMode.Erase, "ERASE_BLEND_MODE")]
 [assembly: LocalizeEnum<BlendMode>(BlendMode.LinearDodge, "LINEAR_DODGE_BLEND_MODE")]
+
+[assembly: LocalizeEnum<GradientType>(GradientType.Linear, "LINEAR_GRADIENT_TYPE")]
+[assembly: LocalizeEnum<GradientType>(GradientType.Radial, "RADIAL_GRADIENT_TYPE")]
+[assembly: LocalizeEnum<GradientType>(GradientType.Conical, "CONICAL_GRADIENT_TYPE")]

+ 4 - 2
src/PixiEditor/Models/Rendering/SceneRenderer.cs

@@ -38,6 +38,7 @@ internal class SceneRenderer : IDisposable
     }
 
     public void RenderScene(DrawingSurface target, ChunkResolution resolution, PointerInfo pointerInfo, SamplingOptions samplingOptions,
+        EditorData editorData,
         string? targetOutput = null)
     {
         if (Document.Renderer.IsBusy || DocumentViewModel.Busy ||
@@ -59,7 +60,7 @@ internal class SceneRenderer : IDisposable
                 cachedTextures[adjustedTargetOutput]?.Dispose();
             }
 
-            var rendered = RenderGraph(target, resolution, samplingOptions, targetOutput, finalGraph, pointerInfo);
+            var rendered = RenderGraph(target, resolution, samplingOptions, targetOutput, finalGraph, pointerInfo, editorData);
             cachedTextures[adjustedTargetOutput] = rendered;
             return;
         }
@@ -83,7 +84,7 @@ internal class SceneRenderer : IDisposable
 
     private Texture RenderGraph(DrawingSurface target, ChunkResolution resolution, SamplingOptions samplingOptions,
         string? targetOutput,
-        IReadOnlyNodeGraph finalGraph, PointerInfo pointerInfo)
+        IReadOnlyNodeGraph finalGraph, PointerInfo pointerInfo, EditorData editorData)
     {
         DrawingSurface renderTarget = target;
         Texture? renderTexture = null;
@@ -120,6 +121,7 @@ internal class SceneRenderer : IDisposable
         RenderContext context = new(renderTarget, DocumentViewModel.AnimationHandler.ActiveFrameTime,
             resolution, finalSize, Document.Size, Document.ProcessingColorSpace, samplingOptions);
         context.PointerInfo = pointerInfo;
+        context.EditorData = editorData;
 
         context.TargetOutput = targetOutput;
         finalGraph.Execute(context);

+ 10 - 0
src/PixiEditor/ViewModels/Document/Nodes/Editor/EditorInfoNodeViewModel.cs

@@ -0,0 +1,10 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Editor;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.Editor;
+
+[NodeViewModel("EDITOR_INFO_NODE", "EDITOR", PixiPerfectIcons.Settings)]
+internal class EditorInfoNodeViewModel : NodeViewModel<EditorInfoNode>
+{
+
+}

+ 10 - 0
src/PixiEditor/ViewModels/Document/Nodes/GradientNodeViewModel.cs

@@ -0,0 +1,10 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes;
+
+[NodeViewModel("GRADIENT_NODE", "COLOR", null)]
+internal class GradientNodeViewModel : NodeViewModel<GradientNode>
+{
+
+}

+ 10 - 0
src/PixiEditor/ViewModels/ViewModelMain.cs

@@ -6,6 +6,7 @@ using Avalonia.Threading;
 using CommunityToolkit.Mvvm.Input;
 using Microsoft.Extensions.DependencyInjection;
 using Drawie.Backend.Core.ColorsImpl;
+using PixiEditor.ChangeableDocument.Rendering.ContextData;
 using PixiEditor.Extensions.CommonApi.UserPreferences;
 using PixiEditor.Helpers;
 using PixiEditor.Helpers.Collections;
@@ -107,6 +108,8 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
 
     public MainWindow? AttachedWindow { get; private set; }
 
+    public Func<EditorData> GetEditorData { get; private set; }
+
     public ViewModelMain()
     {
         Current = this;
@@ -183,6 +186,8 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
 
         ExtensionsSubViewModel.Init();  // Must be last
 
+        GetEditorData = ConstructEditorData;
+
         DocumentManagerSubViewModel.ActiveDocumentChanged += OnActiveDocumentChanged;
         BeforeDocumentClosed += OnBeforeDocumentClosed;
         LazyDocumentClosed += OnLazyDocumentClosed;
@@ -392,6 +397,11 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
         });
     }
 
+    public EditorData ConstructEditorData()
+    {
+        return new EditorData(ColorsSubViewModel.PrimaryColor, ColorsSubViewModel.SecondaryColor);
+    }
+
     private void OnActiveDocumentChanged(object sender, DocumentChangedEventArgs e)
     {
         NotifyToolActionDisplayChanged();

+ 1 - 0
src/PixiEditor/Views/Dock/DocumentTemplate.axaml

@@ -46,6 +46,7 @@
         BackgroundBitmap="{Binding BackgroundBitmap, Mode=OneWay}"
         HudVisible="{Binding HudVisible}"
         MaxBilinearSamplingSize="{Binding ViewportSubViewModel.MaxBilinearSampleSize, Source={viewModels1:MainVM}}"
+        EditorDataFunc="{Binding GetEditorData, Source={viewModels1:MainVM}}"
         Document="{Binding Document}">
     </viewportControls:Viewport>
 </UserControl>

+ 1 - 0
src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml

@@ -258,6 +258,7 @@
             CustomBackgroundScaleY="{Binding CustomBackgroundScaleY, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             BackgroundBitmap="{Binding BackgroundBitmap, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             MaxBilinearSamplingSize="{Binding MaxBilinearSamplingSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
+            EditorDataFunc="{Binding EditorDataFunc, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             PointerPressed="Scene_OnContextMenuOpening"
             ui1:RenderOptionsBindable.BitmapInterpolationMode="{Binding Scale, Converter={converters:ScaleToBitmapScalingModeConverter}, RelativeSource={RelativeSource Self}}">
             <rendering:Scene.ContextFlyout>

+ 12 - 3
src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml.cs

@@ -20,6 +20,7 @@ using PixiEditor.Models.Controllers.InputDevice;
 using PixiEditor.Models.DocumentModels;
 using PixiEditor.Models.Position;
 using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Rendering.ContextData;
 using PixiEditor.Extensions.CommonApi.UserPreferences.Settings.PixiEditor;
 using PixiEditor.UI.Common.Behaviors;
 using PixiEditor.ViewModels.Document;
@@ -382,6 +383,8 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
 
     private MouseUpdateController? mouseUpdateController;
     private ViewportOverlays builtInOverlays = new();
+    public static readonly StyledProperty<Func<EditorData>> EditorDataFuncProperty = AvaloniaProperty.Register<Viewport, Func<EditorData>>("EditorDataFunc");
+
     public static readonly StyledProperty<int> MaxBilinearSamplingSizeProperty
         = AvaloniaProperty.Register<Viewport, int>("MaxBilinearSamplingSize", 4096);
 
@@ -453,6 +456,12 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         set { SetValue(MaxBilinearSamplingSizeProperty, value); }
     }
 
+    public Func<EditorData> EditorDataFunc
+    {
+        get { return (Func<EditorData>)GetValue(EditorDataFuncProperty); }
+        set { SetValue(EditorDataFuncProperty, value); }
+    }
+
     private void ForceRefreshFinalImage()
     {
         Scene.InvalidateVisual();
@@ -534,7 +543,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         var pos = e.GetPosition(Scene);
         VecD scenePos = Scene.ToZoomboxSpace(new VecD(pos.X, pos.Y));
         MouseOnCanvasEventArgs? parameter =
-            new MouseOnCanvasEventArgs(mouseButton, scenePos, e.KeyModifiers, e.ClickCount,
+            new MouseOnCanvasEventArgs(mouseButton, e.Pointer.Type, scenePos, e.KeyModifiers, e.ClickCount,
                 e.GetCurrentPoint(this).Properties);
 
         if (MouseDownCommand.CanExecute(parameter))
@@ -550,7 +559,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
 
         MouseButton mouseButton = e.GetMouseButton(this);
 
-        MouseOnCanvasEventArgs parameter = new(mouseButton, conv, e.KeyModifiers, 0, e.GetCurrentPoint(this).Properties);
+        MouseOnCanvasEventArgs parameter = new(mouseButton, e.Pointer.Type, conv, e.KeyModifiers, 0, e.GetCurrentPoint(this).Properties);
 
         if (MouseMoveCommand.CanExecute(parameter))
             MouseMoveCommand.Execute(parameter);
@@ -563,7 +572,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
 
         Point pos = e.GetPosition(Scene);
         VecD conv = Scene.ToZoomboxSpace(new VecD(pos.X, pos.Y));
-        MouseOnCanvasEventArgs parameter = new(e.InitialPressMouseButton, conv, e.KeyModifiers, 0, e.GetCurrentPoint(this).Properties);
+        MouseOnCanvasEventArgs parameter = new(e.InitialPressMouseButton, e.Pointer.Type, conv, e.KeyModifiers, 0, e.GetCurrentPoint(this).Properties);
         if (MouseUpCommand.CanExecute(parameter))
             MouseUpCommand.Execute(parameter);
     }

+ 3 - 0
src/PixiEditor/Views/Overlays/Pointers/MouseOverlayPointer.cs

@@ -6,6 +6,7 @@ namespace PixiEditor.Views.Overlays.Pointers;
 
 internal class MouseOverlayPointer : IOverlayPointer
 {
+    public PointerType Type { get; }
     IPointer pointer;
     private Action<Overlay?, IPointer> captureAction;
 
@@ -13,8 +14,10 @@ internal class MouseOverlayPointer : IOverlayPointer
     {
         this.pointer = pointer;
         this.captureAction = captureAction;
+        Type = pointer.Type;
     }
 
+
     public void Capture(IOverlay? overlay)
     {
         if (overlay is not Overlay visualOverlay)

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

@@ -667,7 +667,7 @@ internal class TransformOverlay : Overlay
 
         if (!isRotating && !actuallyMoved && pressedWithinBounds)
         {
-            MouseOnCanvasEventArgs args = new(MouseButton.Left, e.Point, e.Modifiers, lastClickCount, e.Properties);
+            MouseOnCanvasEventArgs args = new(MouseButton.Left, e.Pointer.Type, e.Point, e.Modifiers, lastClickCount, e.Properties);
             PassthroughPointerPressedCommand?.Execute(args);
             lastClickCount = 0;
         }

+ 17 - 2
src/PixiEditor/Views/Rendering/Scene.cs

@@ -73,6 +73,15 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
     public static readonly StyledProperty<bool> AutoBackgroundScaleProperty = AvaloniaProperty.Register<Scene, bool>(
         nameof(AutoBackgroundScale), true);
 
+    public static readonly StyledProperty<Func<EditorData>> EditorDataFuncProperty = AvaloniaProperty.Register<Scene, Func<EditorData>>(
+        nameof(EditorDataFunc));
+
+    public Func<EditorData> EditorDataFunc
+    {
+        get => GetValue(EditorDataFuncProperty);
+        set => SetValue(EditorDataFuncProperty, value);
+    }
+
     public bool AutoBackgroundScale
     {
         get => GetValue(AutoBackgroundScaleProperty);
@@ -341,7 +350,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         try
         {
             SceneRenderer.RenderScene(renderTexture.DrawingSurface, CalculateResolution(), lastPointerInfo, CalculateSampling(),
-                renderOutput);
+                EditorDataFunc?.Invoke() ?? new EditorData(), renderOutput);
         }
         catch (Exception e)
         {
@@ -684,11 +693,17 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         }
 
         var properties = data.Properties;
+        float pressure = properties.Pressure;
+        if (e.Pointer.Type != PointerType.Pen)
+        {
+            pressure = pressure > 0 ? 1 : 0;
+        }
+
         VecD position = ToCanvasSpace(data.Position);
         Point dir = lastDirCalculationPoint - data.Position;
         VecD vecDir = new VecD(dir.X, dir.Y);
         VecD dirNormalized = vecDir.Length > 0 ? vecDir.Normalize() : lastPointerInfo.MovementDirection;
-        return new PointerInfo(position, properties.Pressure, properties.Twist, new VecD(properties.XTilt, properties.YTilt), dirNormalized);
+        return new PointerInfo(position, pressure, properties.Twist, new VecD(properties.XTilt, properties.YTilt), dirNormalized);
     }
 
     private static Point Lerp(VecD a, VecD b, float t)