|
@@ -1,7 +1,9 @@
|
|
|
using Avalonia;
|
|
|
using Avalonia.Input;
|
|
|
using Avalonia.Threading;
|
|
|
+using Drawie.Backend.Core.Text;
|
|
|
using Drawie.Numerics;
|
|
|
+using PixiEditor.Helpers.UI;
|
|
|
using PixiEditor.Models.Controllers;
|
|
|
using PixiEditor.Models.Input;
|
|
|
using PixiEditor.OperatingSystem;
|
|
@@ -9,18 +11,8 @@ using Canvas = Drawie.Backend.Core.Surfaces.Canvas;
|
|
|
|
|
|
namespace PixiEditor.Views.Overlays.TextOverlay;
|
|
|
|
|
|
-public class TextOverlay : Overlay
|
|
|
+internal class TextOverlay : Overlay
|
|
|
{
|
|
|
- private Dictionary<KeyCombination, Action> shortcuts;
|
|
|
-
|
|
|
- public TextOverlay()
|
|
|
- {
|
|
|
- shortcuts = new Dictionary<KeyCombination, Action>
|
|
|
- {
|
|
|
- { new KeyCombination(Key.V, KeyModifiers.Control), PasteText },
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
public static readonly StyledProperty<string> TextProperty = AvaloniaProperty.Register<TextOverlay, string>(
|
|
|
nameof(Text));
|
|
|
|
|
@@ -39,46 +31,115 @@ public class TextOverlay : Overlay
|
|
|
set => SetValue(PositionProperty, value);
|
|
|
}
|
|
|
|
|
|
- public static readonly StyledProperty<double> FontSizeProperty = AvaloniaProperty.Register<TextOverlay, double>(
|
|
|
- nameof(FontSize));
|
|
|
+ public static readonly StyledProperty<Font> FontProperty = AvaloniaProperty.Register<TextOverlay, Font>(
|
|
|
+ nameof(Font));
|
|
|
|
|
|
- public double FontSize
|
|
|
+ public Font Font
|
|
|
{
|
|
|
- get => GetValue(FontSizeProperty);
|
|
|
- set => SetValue(FontSizeProperty, value);
|
|
|
+ get => GetValue(FontProperty);
|
|
|
+ set => SetValue(FontProperty, value);
|
|
|
}
|
|
|
|
|
|
+ public static readonly StyledProperty<int> CursorPositionProperty = AvaloniaProperty.Register<TextOverlay, int>(
|
|
|
+ nameof(CursorPosition));
|
|
|
+
|
|
|
+ public int CursorPosition
|
|
|
+ {
|
|
|
+ get => GetValue(CursorPositionProperty);
|
|
|
+ set => SetValue(CursorPositionProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static readonly StyledProperty<int> SelectionLengthProperty = AvaloniaProperty.Register<TextOverlay, int>(
|
|
|
+ nameof(SelectionLength));
|
|
|
+
|
|
|
+ public int SelectionLength
|
|
|
+ {
|
|
|
+ get => GetValue(SelectionLengthProperty);
|
|
|
+ set => SetValue(SelectionLengthProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static readonly StyledProperty<ExecutionTrigger<string>> RequestEditTextProperty =
|
|
|
+ AvaloniaProperty.Register<TextOverlay, ExecutionTrigger<string>>(
|
|
|
+ nameof(RequestEditText));
|
|
|
+
|
|
|
+ public ExecutionTrigger<string> RequestEditText
|
|
|
+ {
|
|
|
+ get => GetValue(RequestEditTextProperty);
|
|
|
+ set => SetValue(RequestEditTextProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static readonly StyledProperty<bool> IsEditingProperty = AvaloniaProperty.Register<TextOverlay, bool>(
|
|
|
+ nameof(IsEditing));
|
|
|
+
|
|
|
+ public bool IsEditing
|
|
|
+ {
|
|
|
+ get => GetValue(IsEditingProperty);
|
|
|
+ set => SetValue(IsEditingProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Dictionary<KeyCombination, Action> shortcuts;
|
|
|
+
|
|
|
+ private Blinker blinker = new Blinker();
|
|
|
+ private VecF[] glyphPositions;
|
|
|
+ private float[] glyphWidths;
|
|
|
+
|
|
|
static TextOverlay()
|
|
|
{
|
|
|
IsVisibleProperty.Changed.Subscribe(IsVisibleChanged);
|
|
|
+ RequestEditTextProperty.Changed.Subscribe(RequestEditTextChanged);
|
|
|
+ IsEditingProperty.Changed.Subscribe(IsEditingChanged);
|
|
|
+ TextProperty.Changed.Subscribe(TextChanged);
|
|
|
+ FontProperty.Changed.Subscribe(FontChanged);
|
|
|
+ }
|
|
|
+
|
|
|
+ public TextOverlay()
|
|
|
+ {
|
|
|
+ shortcuts = new Dictionary<KeyCombination, Action>
|
|
|
+ {
|
|
|
+ { new KeyCombination(Key.V, KeyModifiers.Control), PasteText },
|
|
|
+ { new KeyCombination(Key.Delete, KeyModifiers.None), () => DeleteChar(0) },
|
|
|
+ { new KeyCombination(Key.Back, KeyModifiers.None), () => DeleteChar(-1) },
|
|
|
+ { new KeyCombination(Key.Left, KeyModifiers.None), () => MoveCursorBy(-1) },
|
|
|
+ { new KeyCombination(Key.Right, KeyModifiers.None), () => MoveCursorBy(1) }
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
+
|
|
|
public override void RenderOverlay(Canvas context, RectD canvasBounds)
|
|
|
{
|
|
|
+ if (!IsEditing) return;
|
|
|
+
|
|
|
+ blinker.BlinkerPosition = CursorPosition;
|
|
|
+ blinker.FontSize = Font.Size;
|
|
|
+ blinker.GlyphPositions = glyphPositions;
|
|
|
+ blinker.GlyphWidths = glyphWidths;
|
|
|
+ blinker.Offset = Position;
|
|
|
+
|
|
|
+ blinker.Render(context);
|
|
|
}
|
|
|
|
|
|
protected override void OnKeyPressed(Key key, KeyModifiers keyModifiers)
|
|
|
{
|
|
|
+ if (!IsEditing) return;
|
|
|
if (IsShortcut(key, keyModifiers))
|
|
|
{
|
|
|
ExecuteShortcut(key, keyModifiers);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- if (key == Key.Back)
|
|
|
- {
|
|
|
- if (Text.Length > 0)
|
|
|
- {
|
|
|
- Text = Text[..^1];
|
|
|
- }
|
|
|
- }
|
|
|
- else if (key == Key.Enter)
|
|
|
+ InsertChar(key, keyModifiers);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void InsertChar(Key key, KeyModifiers keyModifiers)
|
|
|
+ {
|
|
|
+ if (key == Key.Enter)
|
|
|
{
|
|
|
Text += Environment.NewLine;
|
|
|
}
|
|
|
else if (key == Key.Space)
|
|
|
{
|
|
|
Text += " ";
|
|
|
+ CursorPosition++;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
@@ -91,6 +152,7 @@ public class TextOverlay : Overlay
|
|
|
{
|
|
|
if (char.IsControl(keyChar.Value)) return;
|
|
|
Text += keyChar;
|
|
|
+ CursorPosition++;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -117,7 +179,51 @@ public class TextOverlay : Overlay
|
|
|
}, TaskContinuationOptions.OnlyOnRanToCompletion);
|
|
|
}
|
|
|
|
|
|
+ private void DeleteChar(int direction)
|
|
|
+ {
|
|
|
+ if (Text.Length > 0)
|
|
|
+ {
|
|
|
+ Text = Text.Remove(CursorPosition + direction, 1);
|
|
|
+ CursorPosition = Math.Clamp(CursorPosition + direction, 0, Text.Length);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void MoveCursorBy(int direction)
|
|
|
+ {
|
|
|
+ CursorPosition = Math.Clamp(CursorPosition + direction, 0, Text.Length);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void RequestEditTextTriggered(object? sender, string e)
|
|
|
+ {
|
|
|
+ IsEditing = true;
|
|
|
+ }
|
|
|
+
|
|
|
private static void IsVisibleChanged(AvaloniaPropertyChangedEventArgs<bool> args)
|
|
|
+ {
|
|
|
+ TextOverlay sender = args.Sender as TextOverlay;
|
|
|
+ if (sender == null) return;
|
|
|
+
|
|
|
+ if (!args.NewValue.Value)
|
|
|
+ {
|
|
|
+ sender.IsEditing = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void RequestEditTextChanged(AvaloniaPropertyChangedEventArgs<ExecutionTrigger<string>> args)
|
|
|
+ {
|
|
|
+ var sender = args.Sender as TextOverlay;
|
|
|
+ if (args.OldValue.Value != null)
|
|
|
+ {
|
|
|
+ args.OldValue.Value.Triggered -= sender.RequestEditTextTriggered;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (args.NewValue.Value != null)
|
|
|
+ {
|
|
|
+ args.NewValue.Value.Triggered += sender.RequestEditTextTriggered;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void IsEditingChanged(AvaloniaPropertyChangedEventArgs<bool> args)
|
|
|
{
|
|
|
if (args.NewValue.Value)
|
|
|
{
|
|
@@ -128,4 +234,24 @@ public class TextOverlay : Overlay
|
|
|
ShortcutController.UnblockShortcutExecution(nameof(TextOverlay));
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ private static void TextChanged(AvaloniaPropertyChangedEventArgs<string> args)
|
|
|
+ {
|
|
|
+ TextOverlay sender = args.Sender as TextOverlay;
|
|
|
+ sender.UpdateGlyphs();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void FontChanged(AvaloniaPropertyChangedEventArgs<Font> args)
|
|
|
+ {
|
|
|
+ TextOverlay sender = args.Sender as TextOverlay;
|
|
|
+ sender.UpdateGlyphs();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void UpdateGlyphs()
|
|
|
+ {
|
|
|
+ if (Font == null) return;
|
|
|
+
|
|
|
+ glyphPositions = Font.GetGlyphPositions(Text);
|
|
|
+ glyphWidths = Font.GetGlyphWidths(Text);
|
|
|
+ }
|
|
|
}
|