瀏覽代碼

Merge branch 'aseprite-space' of https://github.com/PixiEditor/PixiEditor into aseprite-space

Krzysztof Krysiński 1 周之前
父節點
當前提交
56b5654c3d

+ 28 - 19
src/ChunkyImageLib/Operations/EllipseOperation.cs

@@ -8,6 +8,7 @@ using Drawie.Backend.Core.Vector;
 using Drawie.Numerics;
 using Drawie.Numerics;
 
 
 namespace ChunkyImageLib.Operations;
 namespace ChunkyImageLib.Operations;
+
 internal class EllipseOperation : IMirroredDrawOperation
 internal class EllipseOperation : IMirroredDrawOperation
 {
 {
     public bool IgnoreEmptyChunks => false;
     public bool IgnoreEmptyChunks => false;
@@ -21,14 +22,15 @@ internal class EllipseOperation : IMirroredDrawOperation
     private bool init = false;
     private bool init = false;
     private VectorPath? outerPath;
     private VectorPath? outerPath;
     private VectorPath? innerPath;
     private VectorPath? innerPath;
-    
+
     private VectorPath? ellipseOutline;
     private VectorPath? ellipseOutline;
     private VecF[]? ellipse;
     private VecF[]? ellipse;
     private VecF[]? ellipseFill;
     private VecF[]? ellipseFill;
     private RectI? ellipseFillRect;
     private RectI? ellipseFillRect;
     private bool antialiased;
     private bool antialiased;
 
 
-    public EllipseOperation(RectD location, Paintable strokePaintable, Paintable fillPaintable, float strokeWidth, double rotationRad,
+    public EllipseOperation(RectD location, Paintable strokePaintable, Paintable fillPaintable, float strokeWidth,
+        double rotationRad,
         bool antiAliased, Paint? paint = null)
         bool antiAliased, Paint? paint = null)
     {
     {
         this.location = location;
         this.location = location;
@@ -92,7 +94,7 @@ internal class EllipseOperation : IMirroredDrawOperation
 
 
         if (antialiased)
         if (antialiased)
         {
         {
-            DrawAntiAliased(surf);   
+            DrawAntiAliased(surf);
         }
         }
         else
         else
         {
         {
@@ -109,22 +111,26 @@ internal class EllipseOperation : IMirroredDrawOperation
         {
         {
             if (Math.Abs(rotation) < 0.001 && strokeWidth > 0)
             if (Math.Abs(rotation) < 0.001 && strokeWidth > 0)
             {
             {
+                RectD rect = (RectD)ellipseFillRect!.Value;
+                fillPaintable.Bounds = location;
                 if (fillPaintable.AnythingVisible || paint.BlendMode != BlendMode.SrcOver)
                 if (fillPaintable.AnythingVisible || paint.BlendMode != BlendMode.SrcOver)
                 {
                 {
                     paint.SetPaintable(fillPaintable);
                     paint.SetPaintable(fillPaintable);
                     surf.Canvas.DrawPoints(PointMode.Lines, ellipseFill!, paint);
                     surf.Canvas.DrawPoints(PointMode.Lines, ellipseFill!, paint);
-                    surf.Canvas.DrawRect((RectD)ellipseFillRect!.Value, paint);
+                    surf.Canvas.DrawRect(rect, paint);
                 }
                 }
-                
+
                 paint.SetPaintable(strokeWidth <= 0 ? fillPaintable : strokePaintable);
                 paint.SetPaintable(strokeWidth <= 0 ? fillPaintable : strokePaintable);
                 paint.StrokeWidth = 1f;
                 paint.StrokeWidth = 1f;
                 surf.Canvas.DrawPoints(PointMode.Points, ellipse!, paint);
                 surf.Canvas.DrawPoints(PointMode.Points, ellipse!, paint);
+
+                fillPaintable.Bounds = null;
             }
             }
             else
             else
             {
             {
                 surf.Canvas.Save();
                 surf.Canvas.Save();
                 surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
                 surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
-                
+
                 if (fillPaintable.AnythingVisible || paint.BlendMode != BlendMode.SrcOver)
                 if (fillPaintable.AnythingVisible || paint.BlendMode != BlendMode.SrcOver)
                 {
                 {
                     paint.SetPaintable(fillPaintable);
                     paint.SetPaintable(fillPaintable);
@@ -151,14 +157,15 @@ internal class EllipseOperation : IMirroredDrawOperation
                 surf.Canvas.Save();
                 surf.Canvas.Save();
                 surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
                 surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
                 surf.Canvas.ClipPath(innerPath!);
                 surf.Canvas.ClipPath(innerPath!);
-                surf.Canvas.DrawPaintable(fillPaintable, paint.BlendMode);
+                surf.Canvas.DrawPaintable(fillPaintable, paint.BlendMode, location);
                 surf.Canvas.Restore();
                 surf.Canvas.Restore();
             }
             }
+
             surf.Canvas.Save();
             surf.Canvas.Save();
             surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
             surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
             surf.Canvas.ClipPath(outerPath!);
             surf.Canvas.ClipPath(outerPath!);
             surf.Canvas.ClipPath(innerPath!, ClipOperation.Difference);
             surf.Canvas.ClipPath(innerPath!, ClipOperation.Difference);
-            surf.Canvas.DrawPaintable(strokePaintable, paint.BlendMode);
+            surf.Canvas.DrawPaintable(strokePaintable, paint.BlendMode, location);
             surf.Canvas.Restore();
             surf.Canvas.Restore();
         }
         }
     }
     }
@@ -167,24 +174,24 @@ internal class EllipseOperation : IMirroredDrawOperation
     {
     {
         surf.Canvas.Save();
         surf.Canvas.Save();
         surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
         surf.Canvas.RotateRadians((float)rotation, (float)location.Center.X, (float)location.Center.Y);
-        
+
         paint.IsAntiAliased = false;
         paint.IsAntiAliased = false;
         paint.SetPaintable(fillPaintable);
         paint.SetPaintable(fillPaintable);
         paint.Style = PaintStyle.Fill;
         paint.Style = PaintStyle.Fill;
-        
+
         RectD fillRect = ((RectD)location).Inflate(-strokeWidth / 2f);
         RectD fillRect = ((RectD)location).Inflate(-strokeWidth / 2f);
-        
+
         surf.Canvas.DrawOval(fillRect.Center, fillRect.Size / 2f, paint);
         surf.Canvas.DrawOval(fillRect.Center, fillRect.Size / 2f, paint);
 
 
         paint.IsAntiAliased = true;
         paint.IsAntiAliased = true;
         paint.SetPaintable(strokeWidth <= 0 ? fillPaintable : strokePaintable);
         paint.SetPaintable(strokeWidth <= 0 ? fillPaintable : strokePaintable);
         paint.Style = PaintStyle.Stroke;
         paint.Style = PaintStyle.Stroke;
         paint.StrokeWidth = strokeWidth <= 0 ? 1f : strokeWidth;
         paint.StrokeWidth = strokeWidth <= 0 ? 1f : strokeWidth;
-        
+
         RectD strokeRect = ((RectD)location).Inflate((-strokeWidth / 2f));
         RectD strokeRect = ((RectD)location).Inflate((-strokeWidth / 2f));
-        
+
         surf.Canvas.DrawOval(strokeRect.Center, strokeRect.Size / 2f, paint);
         surf.Canvas.DrawOval(strokeRect.Center, strokeRect.Size / 2f, paint);
-        
+
         surf.Canvas.Restore();
         surf.Canvas.Restore();
     }
     }
 
 
@@ -193,14 +200,15 @@ internal class EllipseOperation : IMirroredDrawOperation
         ShapeCorners corners = new((RectD)location);
         ShapeCorners corners = new((RectD)location);
         corners = corners.AsRotated(rotation, (VecD)location.Center);
         corners = corners.AsRotated(rotation, (VecD)location.Center);
         RectI bounds = (RectI)corners.AABBBounds.RoundOutwards();
         RectI bounds = (RectI)corners.AABBBounds.RoundOutwards();
-        
+
         var chunks = OperationHelper.FindChunksTouchingRectangle(bounds, ChunkyImage.FullChunkSize);
         var chunks = OperationHelper.FindChunksTouchingRectangle(bounds, ChunkyImage.FullChunkSize);
         if (!fillPaintable?.AnythingVisible ?? false)
         if (!fillPaintable?.AnythingVisible ?? false)
         {
         {
-             chunks.ExceptWith(OperationHelper.FindChunksFullyInsideEllipse
-                (location.Center, location.Width / 2.0 - strokeWidth * 2, location.Height / 2.0 - strokeWidth * 2, ChunkyImage.FullChunkSize, rotation));
+            chunks.ExceptWith(OperationHelper.FindChunksFullyInsideEllipse
+            (location.Center, location.Width / 2.0 - strokeWidth * 2, location.Height / 2.0 - strokeWidth * 2,
+                ChunkyImage.FullChunkSize, rotation));
         }
         }
-        
+
         return new AffectedArea(chunks, bounds);
         return new AffectedArea(chunks, bounds);
     }
     }
 
 
@@ -226,7 +234,8 @@ internal class EllipseOperation : IMirroredDrawOperation
             ((IPositionPaintable)finalStrokePaintable).Position = newLocation.Center;
             ((IPositionPaintable)finalStrokePaintable).Position = newLocation.Center;
         }
         }
 
 
-        return new EllipseOperation(newLocation, finalStrokePaintable, finalFillPaintable, strokeWidth, rotation, antialiased, paint);
+        return new EllipseOperation(newLocation, finalStrokePaintable, finalFillPaintable, strokeWidth, rotation,
+            antialiased, paint);
     }
     }
 
 
     public void Dispose()
     public void Dispose()

+ 20 - 2
src/ChunkyImageLib/Operations/RectangleOperation.cs

@@ -1,7 +1,9 @@
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
+using Drawie.Backend.Core.ColorsImpl.Paintables;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
+using Drawie.Backend.Core.Utils;
 using Drawie.Numerics;
 using Drawie.Numerics;
 
 
 namespace ChunkyImageLib.Operations;
 namespace ChunkyImageLib.Operations;
@@ -70,7 +72,7 @@ internal class RectangleOperation : IMirroredDrawOperation
                 surf.Canvas.ClipRoundRect(innerRect, vecInnerRadius, ClipOperation.Intersect);
                 surf.Canvas.ClipRoundRect(innerRect, vecInnerRadius, ClipOperation.Intersect);
             }
             }
 
 
-            surf.Canvas.DrawPaintable(Data.FillPaintable, Data.BlendMode);
+            surf.Canvas.DrawPaintable(Data.FillPaintable, Data.BlendMode, rect);
             surf.Canvas.RestoreToCount(saved);
             surf.Canvas.RestoreToCount(saved);
         }
         }
 
 
@@ -88,13 +90,23 @@ internal class RectangleOperation : IMirroredDrawOperation
             surf.Canvas.ClipRoundRect(innerRect, vecInnerRadius, ClipOperation.Difference);
             surf.Canvas.ClipRoundRect(innerRect, vecInnerRadius, ClipOperation.Difference);
         }
         }
 
 
-        surf.Canvas.DrawPaintable(Data.Stroke, Data.BlendMode);
+        surf.Canvas.DrawPaintable(Data.Stroke, Data.BlendMode, rect);
     }
     }
 
 
     private void DrawAntiAliased(DrawingSurface surf, RectD rect, double radius)
     private void DrawAntiAliased(DrawingSurface surf, RectD rect, double radius)
     {
     {
         // shrink radius too so corners match inner curve
         // shrink radius too so corners match inner curve
         // Draw fill first
         // Draw fill first
+        if (Data.FillPaintable != null)
+        {
+            Data.FillPaintable.Bounds = rect;
+        }
+
+        if (Data.Stroke != null)
+        {
+            Data.Stroke.Bounds = rect;
+        }
+
         if (Data.FillPaintable.AnythingVisible)
         if (Data.FillPaintable.AnythingVisible)
         {
         {
             int saved = surf.Canvas.Save();
             int saved = surf.Canvas.Save();
@@ -168,6 +180,12 @@ internal class RectangleOperation : IMirroredDrawOperation
                     (float)innerRadius, (float)innerRadius, paint);
                     (float)innerRadius, (float)innerRadius, paint);
             }
             }
 
 
+            if(Data.FillPaintable != null)
+                Data.FillPaintable.Bounds = null;
+
+            if(Data.Stroke != null)
+                Data.Stroke.Bounds = null;
+
             surf.Canvas.Restore();
             surf.Canvas.Restore();
         }
         }
     }
     }

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit adfaa90105229e3183e3049276af982f3e5b1b5d
+Subproject commit fa8a969891227f91f25f5444be9ad47aab352782

+ 6 - 0
src/PixiEditor/Helpers/SupportedFilesHelper.cs

@@ -86,6 +86,12 @@ internal class SupportedFilesHelper
         string? localPath = file.TryGetLocalPath();
         string? localPath = file.TryGetLocalPath();
 
 
         string extension = Path.GetExtension(localPath ?? file.Name);
         string extension = Path.GetExtension(localPath ?? file.Name);
+
+        if (string.IsNullOrEmpty(extension))
+        {
+            return allSupportedExtensions.First(i => i.CanSave);
+        }
+
         return allSupportedExtensions.Single(i => i.CanSave && i.Extensions.Contains(extension, StringComparer.OrdinalIgnoreCase));
         return allSupportedExtensions.Single(i => i.CanSave && i.Extensions.Contains(extension, StringComparer.OrdinalIgnoreCase));
     }
     }
 
 

+ 1 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorTextToolExecutor.cs

@@ -283,6 +283,7 @@ internal class VectorTextToolExecutor : UpdateableChangeExecutor, ITextOverlayEv
                 new SetLowDpiRendering_Action(selectedMember.Id, toolbar.ForceLowDpiRendering));
                 new SetLowDpiRendering_Action(selectedMember.Id, toolbar.ForceLowDpiRendering));
         }
         }
 
 
+        document.TextOverlayHandler.Font = null; // Forces refreshing glyphs
         document.TextOverlayHandler.Font = constructedText.Font;
         document.TextOverlayHandler.Font = constructedText.Font;
         document.TextOverlayHandler.Spacing = toolbar.Spacing;
         document.TextOverlayHandler.Spacing = toolbar.Spacing;
     }
     }

+ 36 - 26
src/PixiEditor/Views/Main/DocumentPreview.axaml

@@ -11,11 +11,11 @@
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              Name="uc"
              Name="uc"
              x:Class="PixiEditor.Views.Main.DocumentPreview">
              x:Class="PixiEditor.Views.Main.DocumentPreview">
-     <Grid>
+    <Grid>
         <Grid.RowDefinitions>
         <Grid.RowDefinitions>
-            <RowDefinition Height="*"/>
-            <RowDefinition Height="5"/>
-            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*" />
+            <RowDefinition Height="5" />
+            <RowDefinition Height="Auto" />
         </Grid.RowDefinitions>
         </Grid.RowDefinitions>
 
 
         <Grid x:Name="imageGrid" RenderOptions.BitmapInterpolationMode="None"
         <Grid x:Name="imageGrid" RenderOptions.BitmapInterpolationMode="None"
@@ -28,78 +28,88 @@
                 x:Name="viewport"
                 x:Name="viewport"
                 RenderInDocSize="{Binding ElementName=highDpiButton, Path=IsChecked}"
                 RenderInDocSize="{Binding ElementName=highDpiButton, Path=IsChecked}"
                 Document="{Binding Document, ElementName=uc}"
                 Document="{Binding Document, ElementName=uc}"
-                Background="{Binding ActiveItem.Value, ElementName=backgroundButton}"/>
+                Background="{Binding ActiveItem.Value, ElementName=backgroundButton}" />
         </Grid>
         </Grid>
 
 
         <Grid Grid.Row="1">
         <Grid Grid.Row="1">
             <Grid.Background>
             <Grid.Background>
-                <SolidColorBrush Color="{Binding ColorCursorColor, ElementName=uc, FallbackValue=Black}"/>
+                <SolidColorBrush Color="{Binding ColorCursorColor, ElementName=uc, FallbackValue=Black}" />
             </Grid.Background>
             </Grid.Background>
         </Grid>
         </Grid>
         <StackPanel Margin="10, 0, 0, 0" Grid.Row="2" Orientation="Horizontal" Height="30"
         <StackPanel Margin="10, 0, 0, 0" Grid.Row="2" Orientation="Horizontal" Height="30"
                     Background="{DynamicResource ThemeBackgroundBrush}">
                     Background="{DynamicResource ThemeBackgroundBrush}">
             <StackPanel.Styles>
             <StackPanel.Styles>
                 <Style Selector="TextBlock">
                 <Style Selector="TextBlock">
-                    <Setter Property="VerticalAlignment" Value="Center"/>
+                    <Setter Property="VerticalAlignment" Value="Center" />
                 </Style>
                 </Style>
             </StackPanel.Styles>
             </StackPanel.Styles>
 
 
-            <TextBlock Text="{Binding ColorCursorPosition.X, ElementName=uc, StringFormat='X: {0}'}"/>
-            <TextBlock Text="{Binding ColorCursorPosition.Y, ElementName=uc, StringFormat='Y: {0}'}"/>
+            <TextBlock>
+                <Run Text="{Binding ColorCursorPosition.X, ElementName=uc, StringFormat='X: {0}'}" />
+                <Run Text="{Binding ColorCursorPosition.Y, ElementName=uc, StringFormat='Y: {0}'}" />
+            </TextBlock>
 
 
             <TextBlock VerticalAlignment="Center" Margin="10, 0, 0, 0">
             <TextBlock VerticalAlignment="Center" Margin="10, 0, 0, 0">
                 <TextBlock.Text>
                 <TextBlock.Text>
                     <MultiBinding Converter="{converters:FormattedColorConverter}">
                     <MultiBinding Converter="{converters:FormattedColorConverter}">
-                        <Binding Path="ColorCursorColor" ElementName="uc"/>
-                        <Binding Path="ActiveItem.Value" ElementName="formatButton"/>
+                        <Binding Path="ColorCursorColor" ElementName="uc" />
+                        <Binding Path="ActiveItem.Value" ElementName="formatButton" />
                     </MultiBinding>
                     </MultiBinding>
                 </TextBlock.Text>
                 </TextBlock.Text>
             </TextBlock>
             </TextBlock>
         </StackPanel>
         </StackPanel>
-        <Grid Grid.Row="2" HorizontalAlignment="Right" Margin="0,0,5,0" ui:RenderOptionsBindable.BitmapInterpolationMode="{Binding ElementName=backgroundButton, Path=ActiveItem.ScalingMode}">
+        <Grid Grid.Row="2" HorizontalAlignment="Right" Margin="0,0,5,0"
+              ui:RenderOptionsBindable.BitmapInterpolationMode="{Binding ElementName=backgroundButton, Path=ActiveItem.ScalingMode}">
             <StackPanel Spacing="5" Orientation="Horizontal">
             <StackPanel Spacing="5" Orientation="Horizontal">
                 <StackPanel.Styles>
                 <StackPanel.Styles>
                     <Style Selector="ToggleButton#highDpiButton">
                     <Style Selector="ToggleButton#highDpiButton">
-                        <Setter Property="Content" Value="{DynamicResource icon-circle}"/>
+                        <Setter Property="Content" Value="{DynamicResource icon-circle}" />
                     </Style>
                     </Style>
                     <Style Selector="ToggleButton#highDpiButton:checked">
                     <Style Selector="ToggleButton#highDpiButton:checked">
-                        <Setter Property="Content" Value="{DynamicResource icon-lowres-circle}"/>
-                        <Setter Property="Background" Value="Transparent"/>
-                        <Setter Property="BorderThickness" Value="0"/>
+                        <Setter Property="Content" Value="{DynamicResource icon-lowres-circle}" />
+                        <Setter Property="Background" Value="Transparent" />
+                        <Setter Property="BorderThickness" Value="0" />
                     </Style>
                     </Style>
                 </StackPanel.Styles>
                 </StackPanel.Styles>
 
 
-                <ToggleButton x:Name="highDpiButton" Classes="pixi-icon" localization:Translator.TooltipKey="TOGGLE_HIGH_RES_PREVIEW"/>
+                <ToggleButton x:Name="highDpiButton" Classes="pixi-icon"
+                              localization:Translator.TooltipKey="TOGGLE_HIGH_RES_PREVIEW" />
                 <input:ListSwitchButton x:Name="formatButton" Height="20">
                 <input:ListSwitchButton x:Name="formatButton" Height="20">
                     <input:ListSwitchButton.Items>
                     <input:ListSwitchButton.Items>
                         <input:SwitchItemObservableCollection>
                         <input:SwitchItemObservableCollection>
-                            <input:SwitchItem Content="RGBA" Background="{DynamicResource ThemeControlMidBrush}" Value="RGBA"/>
-                            <input:SwitchItem Content="HEX" Background="{DynamicResource ThemeControlMidBrush}" Value="HEX"/>
+                            <input:SwitchItem Content="RGBA" Background="{DynamicResource ThemeControlMidBrush}"
+                                              Value="RGBA" />
+                            <input:SwitchItem Content="HEX" Background="{DynamicResource ThemeControlMidBrush}"
+                                              Value="HEX" />
                         </input:SwitchItemObservableCollection>
                         </input:SwitchItemObservableCollection>
                     </input:ListSwitchButton.Items>
                     </input:ListSwitchButton.Items>
                 </input:ListSwitchButton>
                 </input:ListSwitchButton>
-                <input:ListSwitchButton RenderOptions.BitmapInterpolationMode="None" BorderBrush="{DynamicResource ThemeBorderMidBrush}" Width="25" Height="20" x:Name="backgroundButton">
+                <input:ListSwitchButton RenderOptions.BitmapInterpolationMode="None"
+                                        BorderBrush="{DynamicResource ThemeBorderMidBrush}" Width="25" Height="20"
+                                        x:Name="backgroundButton">
                     <input:ListSwitchButton.Items>
                     <input:ListSwitchButton.Items>
                         <input:SwitchItemObservableCollection>
                         <input:SwitchItemObservableCollection>
                             <input:SwitchItem ScalingMode="None">
                             <input:SwitchItem ScalingMode="None">
                                 <input:SwitchItem.Background>
                                 <input:SwitchItem.Background>
-                                    <ImageBrush Source="/Images/CheckerTile.png" TileMode="Tile" DestinationRect="0, 0, 25 25"/>
+                                    <ImageBrush Source="/Images/CheckerTile.png" TileMode="Tile"
+                                                DestinationRect="0, 0, 25 25" />
                                 </input:SwitchItem.Background>
                                 </input:SwitchItem.Background>
                                 <input:SwitchItem.Value>
                                 <input:SwitchItem.Value>
-                                    <ImageBrush DestinationRect="0, 10, 10, 10" Source="/Images/CheckerTile.png" TileMode="Tile"/>
+                                    <ImageBrush DestinationRect="0, 10, 10, 10" Source="/Images/CheckerTile.png"
+                                                TileMode="Tile" />
                                 </input:SwitchItem.Value>
                                 </input:SwitchItem.Value>
                             </input:SwitchItem>
                             </input:SwitchItem>
                             <input:SwitchItem Value="Transparent">
                             <input:SwitchItem Value="Transparent">
                                 <input:SwitchItem.Background>
                                 <input:SwitchItem.Background>
-                                    <ImageBrush Source="/Images/DiagonalRed.png"/>
+                                    <ImageBrush Source="/Images/DiagonalRed.png" />
                                 </input:SwitchItem.Background>
                                 </input:SwitchItem.Background>
                             </input:SwitchItem>
                             </input:SwitchItem>
-                            <input:SwitchItem Background="White" Value="White"/>
-                            <input:SwitchItem Background="Black" Value="Black"/>
+                            <input:SwitchItem Background="White" Value="White" />
+                            <input:SwitchItem Background="Black" Value="Black" />
                         </input:SwitchItemObservableCollection>
                         </input:SwitchItemObservableCollection>
                     </input:ListSwitchButton.Items>
                     </input:ListSwitchButton.Items>
                 </input:ListSwitchButton>
                 </input:ListSwitchButton>
             </StackPanel>
             </StackPanel>
         </Grid>
         </Grid>
     </Grid>
     </Grid>
-</UserControl>
+</UserControl>

+ 11 - 0
src/PixiEditor/Views/Overlays/Overlay.cs

@@ -160,6 +160,12 @@ public abstract class Overlay : Decorator, IOverlay // TODO: Maybe make it not a
         if (args.Handled) return;
         if (args.Handled) return;
         PointerPressedOverlay?.Invoke(args);
         PointerPressedOverlay?.Invoke(args);
     }
     }
+    
+    public void TextInput(string text)
+    {
+        if(SuppressEvents) return;
+        OnOverlayTextInput(text);
+    }
 
 
     public void ReleasePointer(OverlayPointerArgs args)
     public void ReleasePointer(OverlayPointerArgs args)
     {
     {
@@ -350,6 +356,11 @@ public abstract class Overlay : Decorator, IOverlay // TODO: Maybe make it not a
     {
     {
     }
     }
 
 
+    protected virtual void OnOverlayTextInput(string text)
+    {
+        
+    }
+
     private static void OnZoomScaleChanged(AvaloniaPropertyChangedEventArgs<double> e)
     private static void OnZoomScaleChanged(AvaloniaPropertyChangedEventArgs<double> e)
     {
     {
         if (e.Sender is Overlay overlay)
         if (e.Sender is Overlay overlay)

+ 22 - 19
src/PixiEditor/Views/Overlays/TextOverlay/TextOverlay.cs

@@ -127,6 +127,8 @@ internal class TextOverlay : Overlay
     private Paint opacityPaint;
     private Paint opacityPaint;
     private Paint sampleTextPaint;
     private Paint sampleTextPaint;
 
 
+    private bool canInsertText;
+
     private int lastXMovementCursorIndex;
     private int lastXMovementCursorIndex;
 
 
     static TextOverlay()
     static TextOverlay()
@@ -209,10 +211,7 @@ internal class TextOverlay : Overlay
         };
         };
 
 
         opacityPaint = new Paint() { Color = Colors.White.WithAlpha(ThemeResources.SelectionFillColor.A) };
         opacityPaint = new Paint() { Color = Colors.White.WithAlpha(ThemeResources.SelectionFillColor.A) };
-        sampleTextPaint = new Paint()
-        {
-            Color = Colors.Black, Style = PaintStyle.Fill, IsAntiAliased = true
-        };
+        sampleTextPaint = new Paint() { Color = Colors.Black, Style = PaintStyle.Fill, IsAntiAliased = true };
     }
     }
 
 
 
 
@@ -472,6 +471,13 @@ internal class TextOverlay : Overlay
         return indexOfClosest;
         return indexOfClosest;
     }
     }
 
 
+    protected override void OnOverlayTextInput(string text)
+    {
+        if (!IsEditing || !canInsertText) return;
+
+        InsertTextAtCursor(text);
+    }
+
     protected override void OnKeyPressed(KeyEventArgs args)
     protected override void OnKeyPressed(KeyEventArgs args)
     {
     {
         if (!IsEditing) return;
         if (!IsEditing) return;
@@ -484,16 +490,23 @@ internal class TextOverlay : Overlay
         if (IsRegisteredExternalShortcut(key, keyModifiers))
         if (IsRegisteredExternalShortcut(key, keyModifiers))
         {
         {
             ShortcutController.UnblockShortcutExecution(nameof(TextOverlay));
             ShortcutController.UnblockShortcutExecution(nameof(TextOverlay));
+            canInsertText = false;
             return;
             return;
         }
         }
 
 
         if (IsShortcut(key, keyModifiers))
         if (IsShortcut(key, keyModifiers))
         {
         {
             ExecuteShortcut(key, keyModifiers);
             ExecuteShortcut(key, keyModifiers);
+            canInsertText = false;
             return;
             return;
         }
         }
 
 
-        InsertChar(key, args.KeySymbol);
+        if (key == Key.Tab)
+        {
+            args.Handled = true;
+        }
+
+        canInsertText = !TryInsertSpecialChar(key, args.KeySymbol);
     }
     }
 
 
     private bool IsRegisteredExternalShortcut(Key key, KeyModifiers keyModifiers)
     private bool IsRegisteredExternalShortcut(Key key, KeyModifiers keyModifiers)
@@ -506,25 +519,15 @@ internal class TextOverlay : Overlay
         return ctxCommand != null;
         return ctxCommand != null;
     }
     }
 
 
-    private void InsertChar(Key key, string symbol)
+    private bool TryInsertSpecialChar(Key key, string symbol)
     {
     {
         if (key == Key.Enter)
         if (key == Key.Enter)
         {
         {
             InsertTextAtCursor("\n");
             InsertTextAtCursor("\n");
+            return true;
         }
         }
-        else if (key == Key.Space)
-        {
-            InsertTextAtCursor(" ");
-        }
-        else
-        {
-            if (symbol is { Length: 1 })
-            {
-                char symbolChar = symbol[0];
-                if (char.IsControl(symbolChar)) return;
-                InsertTextAtCursor(symbol);
-            }
-        }
+
+        return false;
     }
     }
 
 
     private void InsertTextAtCursor(string toAdd)
     private void InsertTextAtCursor(string toAdd)

+ 20 - 0
src/PixiEditor/Views/Rendering/Scene.cs

@@ -647,6 +647,26 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         }
         }
     }
     }
 
 
+    protected override void OnTextInput(TextInputEventArgs e)
+    {
+        base.OnTextInput(e);
+        try
+        {
+            if (AllOverlays != null)
+            {
+                foreach (Overlay overlay in AllOverlays)
+                {
+                    if (!overlay.IsVisible) continue;
+                    overlay.TextInput(e.Text);
+                }
+            }
+        }
+        catch (Exception ex)
+        {
+            CrashHelper.SendExceptionInfo(ex);
+        }
+    }
+
     private OverlayPointerArgs ConstructPointerArgs(PointerEventArgs e)
     private OverlayPointerArgs ConstructPointerArgs(PointerEventArgs e)
     {
     {
         return new OverlayPointerArgs
         return new OverlayPointerArgs