Browse Source

Basic logic of text tool works

flabbet 6 months ago
parent
commit
b70af65092

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit 2f3d129a0f1283b9ab34908e965507d689c3c066
+Subproject commit c0bcbd1558af511bdd361232b3c5ae6ffadebdb6

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

@@ -15,7 +15,7 @@ public abstract class ShapeVectorData : ICacheable, ICloneable, IReadOnlyShapeVe
     
     
     public Color StrokeColor { get; set; } = Colors.White;
     public Color StrokeColor { get; set; } = Colors.White;
     public Color FillColor { get; set; } = Colors.White;
     public Color FillColor { get; set; } = Colors.White;
-    public float StrokeWidth { get; set; } = 1;
+    public float StrokeWidth { get; set; } = 0;
     public bool Fill { get; set; } = true;
     public bool Fill { get; set; } = true;
     public abstract RectD GeometryAABB { get; } 
     public abstract RectD GeometryAABB { get; } 
     public abstract RectD VisualAABB { get; }
     public abstract RectD VisualAABB { get; }

+ 37 - 47
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/TextVectorData.cs

@@ -1,4 +1,5 @@
-using Drawie.Backend.Core.Surfaces;
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Text;
 using Drawie.Backend.Core.Text;
 using Drawie.Backend.Core.Vector;
 using Drawie.Backend.Core.Vector;
@@ -6,9 +7,20 @@ using Drawie.Numerics;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 
 
-public class TextVectorData : ShapeVectorData
+public class TextVectorData : ShapeVectorData, IDisposable
 {
 {
-    public string Text { get; set; }
+    private string text;
+
+    public string Text
+    {
+        get => text;
+        set
+        {
+            text = value;
+            richText = new RichText(value);
+        }
+    }
+
     public VecD Position { get; set; }
     public VecD Position { get; set; }
     public Font Font { get; set; } = Font.CreateDefault();
     public Font Font { get; set; } = Font.CreateDefault();
 
 
@@ -16,18 +28,7 @@ public class TextVectorData : ShapeVectorData
     {
     {
         get
         get
         {
         {
-            PaintStyle style = PaintStyle.StrokeAndFill;
-            if(!Fill && StrokeWidth > 0)
-            {
-                style = PaintStyle.Stroke;
-            }
-            else if(Fill && FillColor.A > 0 && StrokeWidth <= 0)
-            {
-                style = PaintStyle.Fill;
-            }
-            
-            using Paint paint = new Paint() { StrokeWidth = StrokeWidth, Style = style };
-            Font.MeasureText(Text, out RectD bounds, paint);
+            RectD bounds = richText.MeasureBounds(Font);
             return bounds.Offset(Position);
             return bounds.Offset(Position);
         }
         }
     }
     }
@@ -37,12 +38,14 @@ public class TextVectorData : ShapeVectorData
 
 
     public override RectD VisualAABB => GeometryAABB;
     public override RectD VisualAABB => GeometryAABB;
     public VectorPath? Path { get; set; }
     public VectorPath? Path { get; set; }
+    
+    private RichText richText;
 
 
     public override VectorPath ToPath()
     public override VectorPath ToPath()
     {
     {
         var path = Font.GetTextPath(Text);
         var path = Font.GetTextPath(Text);
         path.Offset(Position);
         path.Offset(Position);
-        
+
         return path;
         return path;
     }
     }
 
 
@@ -65,38 +68,14 @@ public class TextVectorData : ShapeVectorData
             ApplyTransformTo(canvas);
             ApplyTransformTo(canvas);
         }
         }
 
 
-        using Paint paint = new Paint() { IsAntiAliased = true, };
-
-        if (Fill && FillColor.A > 0)
-        {
-            paint.Color = FillColor;
-            paint.Style = PaintStyle.Fill;
-
-            if (Path == null)
-            {
-                canvas.DrawText(Text, Position, Font, paint);
-            }
-            else
-            {
-                canvas.DrawTextOnPath(Path, Text, Position, Font, paint);
-            }
-        }
+        using Paint paint = new Paint() { IsAntiAliased = true };
+        
+        richText.Fill = Fill;
+        richText.FillColor = FillColor;
+        richText.StrokeColor = StrokeColor;
+        richText.StrokeWidth = StrokeWidth;
 
 
-        if (StrokeWidth > 0)
-        {
-            paint.Color = StrokeColor;
-            paint.Style = PaintStyle.Stroke;
-            paint.StrokeWidth = StrokeWidth;
-
-            if (Path == null)
-            {
-                canvas.DrawText(Text, Position, Font, paint);
-            }
-            else
-            {
-                canvas.DrawTextOnPath(Path, Text, Position, Font, paint);
-            }
-        }
+        PaintText(canvas, paint);
 
 
         if (applyTransform)
         if (applyTransform)
         {
         {
@@ -104,6 +83,11 @@ public class TextVectorData : ShapeVectorData
         }
         }
     }
     }
 
 
+    private void PaintText(Canvas canvas, Paint paint)
+    {
+        richText.Paint(canvas, Position, Font, paint, Path);   
+    }
+
     public override bool IsValid()
     public override bool IsValid()
     {
     {
         return !string.IsNullOrEmpty(Text);
         return !string.IsNullOrEmpty(Text);
@@ -118,4 +102,10 @@ public class TextVectorData : ShapeVectorData
     {
     {
         return GetCacheHash();
         return GetCacheHash();
     }
     }
+
+    public void Dispose()
+    {
+        Font.Dispose();
+        Path?.Dispose();
+    }
 }
 }

+ 5 - 0
src/PixiEditor.ChangeableDocument/Changes/Vectors/SetShapeGeometry_UpdateableChange.cs

@@ -42,6 +42,11 @@ internal class SetShapeGeometry_UpdateableChange : InterruptableUpdateableChange
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> ApplyTemporarily(Document target)
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> ApplyTemporarily(Document target)
     {
     {
         var node = target.FindNode<VectorLayerNode>(TargetId);
         var node = target.FindNode<VectorLayerNode>(TargetId);
+        if(node.ShapeData is IDisposable disposable)
+        {
+            disposable.Dispose();
+        }
+        
         node.ShapeData = Data;
         node.ShapeData = Data;
 
 
         RectD aabb = node.ShapeData.TransformedAABB.RoundOutwards();
         RectD aabb = node.ShapeData.TransformedAABB.RoundOutwards();

+ 8 - 0
src/PixiEditor/Models/DocumentModels/ChangeExecutionController.cs

@@ -305,4 +305,12 @@ internal class ChangeExecutionController
             vectorPathToolExecutor.OnPathChanged(path);
             vectorPathToolExecutor.OnPathChanged(path);
         }
         }
     }
     }
+
+    public void TextOverlayTextChangedInlet(string text)
+    {
+        if (currentSession is ITextOverlayEvents textOverlayHandler)
+        {
+            textOverlayHandler.OnTextChanged(text);
+        }
+    }
 }
 }

+ 6 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/Features/ITextOverlayEvents.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
+
+public interface ITextOverlayEvents :IExecutorFeature
+{
+    public void OnTextChanged(string text);
+}

+ 71 - 10
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorTextToolExecutor.cs

@@ -1,6 +1,11 @@
-using Drawie.Numerics;
+using Avalonia.Input;
+using Drawie.Backend.Core.ColorsImpl;
+using Drawie.Backend.Core.Numerics;
+using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
+using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.DocumentModels.UpdateableChangeExecutors.Features;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers.Toolbars;
 using PixiEditor.Models.Handlers.Toolbars;
 using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.Models.Handlers.Tools;
@@ -8,10 +13,15 @@ using PixiEditor.Models.Tools;
 
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 
 
-internal class VectorTextToolExecutor : UpdateableChangeExecutor
+internal class VectorTextToolExecutor : UpdateableChangeExecutor, ITextOverlayEvents
 {
 {
     private ITextToolHandler textHandler;
     private ITextToolHandler textHandler;
     private IFillableShapeToolbar toolbar;
     private IFillableShapeToolbar toolbar;
+    private IStructureMemberHandler selectedMember;
+
+    private string lastText = "";
+    private VecD position;
+    private Matrix3X3 lastMatrix = Matrix3X3.Identity;
 
 
     public override ExecutionState Start()
     public override ExecutionState Start()
     {
     {
@@ -27,7 +37,7 @@ internal class VectorTextToolExecutor : UpdateableChangeExecutor
             return ExecutionState.Error;
             return ExecutionState.Error;
         }
         }
 
 
-        var selectedMember = document.SelectedStructureMember;
+        selectedMember = document.SelectedStructureMember;
 
 
         if (selectedMember is not IVectorLayerHandler layerHandler)
         if (selectedMember is not IVectorLayerHandler layerHandler)
         {
         {
@@ -35,21 +45,72 @@ internal class VectorTextToolExecutor : UpdateableChangeExecutor
         }
         }
 
 
         var shape = layerHandler.GetShapeData(document.AnimationHandler.ActiveFrameBindable);
         var shape = layerHandler.GetShapeData(document.AnimationHandler.ActiveFrameBindable);
-        if (shape != null && shape is not TextVectorData textData)
+        if (shape is TextVectorData textData)
+        {
+            document.TextOverlayHandler.Show(textData.Text, textData.Position, textData.Font.Size);
+            lastText = textData.Text;
+            position = textData.Position;
+            lastMatrix = textData.TransformationMatrix;
+        }
+        else if (shape is null)
+        {
+            document.TextOverlayHandler.Show("", controller.LastPrecisePosition, 12);
+            lastText = "";
+            position = controller.LastPrecisePosition;
+        }
+        else
         {
         {
             return ExecutionState.Error;
             return ExecutionState.Error;
         }
         }
 
 
-        internals.ActionAccumulator.AddFinishedActions(
-            new SetShapeGeometry_Action(selectedMember.Id,
-                new TextVectorData() { Text = "Test", Position = document.SizeBindable / 2f }),
-            new EndSetShapeGeometry_Action());
-        //document.TextHandler.ShowOverlay(textData.Text
-
         return ExecutionState.Success;
         return ExecutionState.Success;
     }
     }
 
 
     public override void ForceStop()
     public override void ForceStop()
     {
     {
+        internals.ActionAccumulator.AddFinishedActions(new EndSetShapeGeometry_Action());
+        document.TextOverlayHandler.Hide();
+    }
+
+    public void OnTextChanged(string text)
+    {
+        internals.ActionAccumulator.AddActions(new SetShapeGeometry_Action(selectedMember.Id, ConstructTextData(text)));
+        lastText = text;
+    }
+
+    public override void OnSettingsChanged(string name, object value)
+    {
+        internals.ActionAccumulator.AddActions(new SetShapeGeometry_Action(selectedMember.Id,
+            ConstructTextData(lastText)));
+    }
+
+    public override void OnColorChanged(Color color, bool primary)
+    {
+        if (!primary || !toolbar.SyncWithPrimaryColor)
+        {
+             return;
+        }
+
+        toolbar.StrokeColor = color.ToColor();
+        toolbar.FillColor = color.ToColor();
+    }
+
+    private TextVectorData ConstructTextData(string text)
+    {
+        return new TextVectorData()
+        {
+            Text = text,
+            Position = position,
+            Fill = toolbar.Fill,
+            FillColor = toolbar.FillColor.ToColor(),
+            StrokeWidth = (float)toolbar.ToolSize,
+            StrokeColor = toolbar.StrokeColor.ToColor(),
+            TransformationMatrix = lastMatrix
+        };
+    }
+
+    bool IExecutorFeature.IsFeatureEnabled(IExecutorFeature feature)
+    {
+        return feature is ITextOverlayEvents;
     }
     }
 }
 }

+ 5 - 1
src/PixiEditor/Models/Handlers/IDocument.cs

@@ -36,6 +36,7 @@ internal interface IDocument : IHandler
     public IReadOnlyCollection<IStructureMemberHandler> SoftSelectedStructureMembers { get; }
     public IReadOnlyCollection<IStructureMemberHandler> SoftSelectedStructureMembers { get; }
     public ITransformHandler TransformHandler { get; }
     public ITransformHandler TransformHandler { get; }
     public IPathOverlayHandler PathOverlayHandler { get; }
     public IPathOverlayHandler PathOverlayHandler { get; }
+    public ITextOverlayHandler TextOverlayHandler { get; }
     public bool Busy { get; set; }
     public bool Busy { get; set; }
     public ILineOverlayHandler LineToolOverlayHandler { get; }
     public ILineOverlayHandler LineToolOverlayHandler { get; }
     public bool HorizontalSymmetryAxisEnabledBindable { get; }
     public bool HorizontalSymmetryAxisEnabledBindable { get; }
@@ -57,7 +58,10 @@ internal interface IDocument : IHandler
     public void UpdateSelectionPath(VectorPath infoNewPath);
     public void UpdateSelectionPath(VectorPath infoNewPath);
     public void SetProcessingColorSpace(ColorSpace infoColorSpace);
     public void SetProcessingColorSpace(ColorSpace infoColorSpace);
     public void SetSize(VecI infoSize);
     public void SetSize(VecI infoSize);
-    public Color PickColor(VecD controllerLastPrecisePosition, DocumentScope scope, bool includeReference, bool includeCanvas, int frame, bool isTopMost);
+
+    public Color PickColor(VecD controllerLastPrecisePosition, DocumentScope scope, bool includeReference,
+        bool includeCanvas, int frame, bool isTopMost);
+
     public HashSet<Guid> ExtractSelectedLayers(bool includeFoldersWithMask = false);
     public HashSet<Guid> ExtractSelectedLayers(bool includeFoldersWithMask = false);
     public void UpdateSavedState();
     public void UpdateSavedState();
 
 

+ 9 - 0
src/PixiEditor/Models/Handlers/ITextOverlayHandler.cs

@@ -0,0 +1,9 @@
+using Drawie.Numerics;
+
+namespace PixiEditor.Models.Handlers;
+
+public interface ITextOverlayHandler : IHandler
+{
+    public void Show(string text, VecD position, double fontSize);
+    public void Hide();
+}

+ 9 - 0
src/PixiEditor/ViewModels/Document/DocumentViewModel.cs

@@ -70,6 +70,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
 
 
     private bool busy = false;
     private bool busy = false;
 
 
+
     public bool Busy
     public bool Busy
     {
     {
         get => busy;
         get => busy;
@@ -208,12 +209,14 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
     public ReferenceLayerViewModel ReferenceLayerViewModel { get; }
     public ReferenceLayerViewModel ReferenceLayerViewModel { get; }
     public LineToolOverlayViewModel LineToolOverlayViewModel { get; }
     public LineToolOverlayViewModel LineToolOverlayViewModel { get; }
     public AnimationDataViewModel AnimationDataViewModel { get; }
     public AnimationDataViewModel AnimationDataViewModel { get; }
+    public TextOverlayViewModel TextOverlayViewModel { get; }
 
 
     public IReadOnlyCollection<IStructureMemberHandler> SoftSelectedStructureMembers => softSelectedStructureMembers;
     public IReadOnlyCollection<IStructureMemberHandler> SoftSelectedStructureMembers => softSelectedStructureMembers;
     private DocumentInternalParts Internals { get; }
     private DocumentInternalParts Internals { get; }
     INodeGraphHandler IDocument.NodeGraphHandler => NodeGraph;
     INodeGraphHandler IDocument.NodeGraphHandler => NodeGraph;
     IDocumentOperations IDocument.Operations => Operations;
     IDocumentOperations IDocument.Operations => Operations;
     ITransformHandler IDocument.TransformHandler => TransformViewModel;
     ITransformHandler IDocument.TransformHandler => TransformViewModel;
+    ITextOverlayHandler IDocument.TextOverlayHandler => TextOverlayViewModel;
     IPathOverlayHandler IDocument.PathOverlayHandler => PathOverlayViewModel;
     IPathOverlayHandler IDocument.PathOverlayHandler => PathOverlayViewModel;
     ILineOverlayHandler IDocument.LineToolOverlayHandler => LineToolOverlayViewModel;
     ILineOverlayHandler IDocument.LineToolOverlayHandler => LineToolOverlayViewModel;
     IReferenceLayerHandler IDocument.ReferenceLayerHandler => ReferenceLayerViewModel;
     IReferenceLayerHandler IDocument.ReferenceLayerHandler => ReferenceLayerViewModel;
@@ -250,6 +253,12 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
         LineToolOverlayViewModel.LineMoved += (_, args) =>
         LineToolOverlayViewModel.LineMoved += (_, args) =>
             Internals.ChangeController.LineOverlayMovedInlet(args.Item1, args.Item2);
             Internals.ChangeController.LineOverlayMovedInlet(args.Item1, args.Item2);
 
 
+        TextOverlayViewModel = new TextOverlayViewModel();
+        TextOverlayViewModel.TextChanged += text =>
+        {
+            Internals.ChangeController.TextOverlayTextChangedInlet(text);
+        };
+
         SnappingViewModel = new();
         SnappingViewModel = new();
         SnappingViewModel.AddFromDocumentSize(SizeBindable);
         SnappingViewModel.AddFromDocumentSize(SizeBindable);
         SizeChanged += (_, args) =>
         SizeChanged += (_, args) =>

+ 59 - 0
src/PixiEditor/ViewModels/Document/TransformOverlays/TextOverlayViewModel.cs

@@ -0,0 +1,59 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using Drawie.Numerics;
+using PixiEditor.Models.Handlers;
+
+namespace PixiEditor.ViewModels.Document.TransformOverlays;
+
+public class TextOverlayViewModel : ObservableObject, ITextOverlayHandler
+{
+    private bool isActive;
+    private string text;
+    private VecD position;
+    private double fontSize;
+    
+    public event Action<string>? TextChanged;
+
+    public bool IsActive
+    {
+        get => isActive;
+        set => SetProperty(ref isActive, value);
+    }
+
+    public string Text
+    {
+        get => text;
+        set
+        {
+            SetProperty(ref text, value);
+            if (IsActive)
+            {
+                TextChanged?.Invoke(value);
+            }
+        }
+    }
+
+    public VecD Position
+    {
+        get => position;
+        set => SetProperty(ref position, value);
+    }
+
+    public double FontSize
+    {
+        get => fontSize;
+        set => SetProperty(ref fontSize, value);
+    }
+
+    public void Show(string text, VecD position, double fontSize)
+    {
+        IsActive = true;
+        Text = text;
+        Position = position;
+        FontSize = fontSize;
+    }
+    
+    public void Hide()
+    {
+        IsActive = false;
+    }
+}

+ 15 - 0
src/PixiEditor/ViewModels/Tools/Tools/TextToolViewModel.cs

@@ -3,6 +3,7 @@ using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Commands.Attributes.Commands;
+using PixiEditor.Models.Handlers;
 using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.ViewModels.Tools.ToolSettings.Toolbars;
 using PixiEditor.ViewModels.Tools.ToolSettings.Toolbars;
 
 
@@ -31,9 +32,23 @@ internal class TextToolViewModel : ToolViewModel, ITextToolHandler
 
 
     protected override void OnSelected(bool restoring)
     protected override void OnSelected(bool restoring)
     {
     {
+        if (!restoring)
+        {
+            ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseVectorTextTool();
+        }
     }
     }
 
 
     protected override void OnDeselecting(bool transient)
     protected override void OnDeselecting(bool transient)
     {
     {
+        if (!transient)
+        {
+            ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Operations.TryStopToolLinkedExecutor();
+        }
+    }
+
+    protected override void OnSelectedLayersChanged(IStructureMemberHandler[] layers)
+    {
+        OnDeselecting(false);
+        OnSelected(false);
     }
     }
 }
 }

+ 35 - 0
src/PixiEditor/Views/Main/ViewportControls/ViewportOverlays.cs

@@ -7,6 +7,7 @@ using PixiEditor.Views.Visuals;
 using PixiEditor.Helpers.Converters;
 using PixiEditor.Helpers.Converters;
 using PixiEditor.Models.Commands.XAML;
 using PixiEditor.Models.Commands.XAML;
 using PixiEditor.ViewModels;
 using PixiEditor.ViewModels;
+using PixiEditor.ViewModels.Document.TransformOverlays;
 using PixiEditor.Views.Overlays;
 using PixiEditor.Views.Overlays;
 using PixiEditor.Views.Overlays.BrushShapeOverlay;
 using PixiEditor.Views.Overlays.BrushShapeOverlay;
 using PixiEditor.Views.Overlays.LineToolOverlay;
 using PixiEditor.Views.Overlays.LineToolOverlay;
@@ -14,6 +15,7 @@ using PixiEditor.Views.Overlays.PathOverlay;
 using PixiEditor.Views.Overlays.Pointers;
 using PixiEditor.Views.Overlays.Pointers;
 using PixiEditor.Views.Overlays.SelectionOverlay;
 using PixiEditor.Views.Overlays.SelectionOverlay;
 using PixiEditor.Views.Overlays.SymmetryOverlay;
 using PixiEditor.Views.Overlays.SymmetryOverlay;
+using PixiEditor.Views.Overlays.TextOverlay;
 using PixiEditor.Views.Overlays.TransformOverlay;
 using PixiEditor.Views.Overlays.TransformOverlay;
 
 
 namespace PixiEditor.Views.Main.ViewportControls;
 namespace PixiEditor.Views.Main.ViewportControls;
@@ -31,6 +33,7 @@ internal class ViewportOverlays
     private SnappingOverlay snappingOverlay;
     private SnappingOverlay snappingOverlay;
     private BrushShapeOverlay brushShapeOverlay;
     private BrushShapeOverlay brushShapeOverlay;
     private VectorPathOverlay vectorPathOverlay;
     private VectorPathOverlay vectorPathOverlay;
+    private TextOverlay textOverlay;
 
 
     public void Init(Viewport viewport)
     public void Init(Viewport viewport)
     {
     {
@@ -62,6 +65,9 @@ internal class ViewportOverlays
         vectorPathOverlay = new VectorPathOverlay();
         vectorPathOverlay = new VectorPathOverlay();
         vectorPathOverlay.IsVisible = false;
         vectorPathOverlay.IsVisible = false;
         BindVectorPathOverlay();
         BindVectorPathOverlay();
+        
+        textOverlay = new TextOverlay();
+        BindTextOverlay();
 
 
         Viewport.ActiveOverlays.Add(gridLinesOverlay);
         Viewport.ActiveOverlays.Add(gridLinesOverlay);
         Viewport.ActiveOverlays.Add(referenceLayerOverlay);
         Viewport.ActiveOverlays.Add(referenceLayerOverlay);
@@ -72,6 +78,7 @@ internal class ViewportOverlays
         Viewport.ActiveOverlays.Add(vectorPathOverlay);
         Viewport.ActiveOverlays.Add(vectorPathOverlay);
         Viewport.ActiveOverlays.Add(snappingOverlay);
         Viewport.ActiveOverlays.Add(snappingOverlay);
         Viewport.ActiveOverlays.Add(brushShapeOverlay);
         Viewport.ActiveOverlays.Add(brushShapeOverlay);
+        Viewport.ActiveOverlays.Add(textOverlay);
     }
     }
 
 
     private void BindReferenceLayerOverlay()
     private void BindReferenceLayerOverlay()
@@ -443,5 +450,33 @@ internal class ViewportOverlays
         brushShapeOverlay.Bind(BrushShapeOverlay.BrushSizeProperty, brushSizeBinding);
         brushShapeOverlay.Bind(BrushShapeOverlay.BrushSizeProperty, brushSizeBinding);
         brushShapeOverlay.Bind(BrushShapeOverlay.BrushShapeProperty, brushShapeBinding); 
         brushShapeOverlay.Bind(BrushShapeOverlay.BrushShapeProperty, brushShapeBinding); 
     }
     }
+
+    private void BindTextOverlay()
+    {
+        Binding isVisibleBinding = new()
+        {
+            Source = Viewport, Path = "Document.TextOverlayViewModel.IsActive", Mode = BindingMode.OneWay
+        };
+
+        Binding textBinding = new()
+        {
+            Source = Viewport, Path = "Document.TextOverlayViewModel.Text", Mode = BindingMode.TwoWay
+        };
+
+        Binding positionBinding = new()
+        {
+            Source = Viewport, Path = "Document.TextOverlayViewModel.Position", Mode = BindingMode.OneWay
+        };
+
+        Binding fontSizeBinding = new()
+        {
+            Source = Viewport, Path = "Document.TextOverlayViewModel.FontSize", Mode = BindingMode.OneWay
+        };
+
+        textOverlay.Bind(Visual.IsVisibleProperty, isVisibleBinding);
+        textOverlay.Bind(TextOverlay.TextProperty, textBinding);
+        textOverlay.Bind(TextOverlay.PositionProperty, positionBinding);
+        textOverlay.Bind(TextOverlay.FontSizeProperty, fontSizeBinding);
+    }
 }
 }
 
 

+ 91 - 0
src/PixiEditor/Views/Overlays/TextOverlay/TextOverlay.cs

@@ -0,0 +1,91 @@
+using Avalonia;
+using Avalonia.Input;
+using Drawie.Numerics;
+using PixiEditor.Models.Controllers;
+using PixiEditor.OperatingSystem;
+using Canvas = Drawie.Backend.Core.Surfaces.Canvas;
+
+namespace PixiEditor.Views.Overlays.TextOverlay;
+
+public class TextOverlay : Overlay
+{
+    public static readonly StyledProperty<string> TextProperty = AvaloniaProperty.Register<TextOverlay, string>(
+        nameof(Text));
+
+    public string Text
+    {
+        get => GetValue(TextProperty);
+        set => SetValue(TextProperty, value);
+    }
+
+    public static readonly StyledProperty<VecD> PositionProperty = AvaloniaProperty.Register<TextOverlay, VecD>(
+        nameof(Position));
+
+    public VecD Position
+    {
+        get => GetValue(PositionProperty);
+        set => SetValue(PositionProperty, value);
+    }
+
+    public static readonly StyledProperty<double> FontSizeProperty = AvaloniaProperty.Register<TextOverlay, double>(
+        nameof(FontSize));
+
+    public double FontSize
+    {
+        get => GetValue(FontSizeProperty);
+        set => SetValue(FontSizeProperty, value);
+    }
+
+    static TextOverlay()
+    {
+        IsVisibleProperty.Changed.Subscribe(IsVisibleChanged);
+    }
+
+    public override void RenderOverlay(Canvas context, RectD canvasBounds)
+    {
+    }
+
+    protected override void OnKeyPressed(Key key, KeyModifiers keyModifiers)
+    {
+        if (key == Key.Back)
+        {
+            if (Text.Length > 0)
+            {
+                Text = Text[..^1];
+            }
+        }
+        else if (key == Key.Enter)
+        {
+            Text += Environment.NewLine;
+        }
+        else if (key == Key.Space)
+        {
+            Text += " ";
+        }
+        else
+        {
+            string converted = IOperatingSystem.Current.InputKeys.GetKeyboardKey(key);
+            if(converted == null || converted.Length > 1) return;
+            
+            string toAdd = keyModifiers.HasFlag(KeyModifiers.Shift) ? converted.ToUpper() : converted.ToLower();
+            char? keyChar = toAdd.FirstOrDefault();
+            if (keyChar != null)
+            {
+                if(char.IsControl(keyChar.Value)) return;
+                Text += keyChar;
+            }
+        }
+    }
+
+    private static void IsVisibleChanged(AvaloniaPropertyChangedEventArgs<bool> args)
+    {
+        if (args.NewValue.Value)
+        {
+            ShortcutController.BlockShortcutExecution(nameof(TextOverlay));
+        }
+        else
+        {
+            ShortcutController.UnblockShortcutExecution(nameof(TextOverlay));
+        }
+    }
+}