Browse Source

use spans for text ranges

android_text_client
Emmanuel Hansen 4 months ago
parent
commit
5fa48041af
  1. 2
      src/Android/Avalonia.Android/Platform/Input/AndroidInputMethod.cs
  2. 2
      src/Android/Avalonia.Android/Platform/Input/AvaloniaInputConnection.cs
  3. 29
      src/Android/Avalonia.Android/Platform/Input/TextEditBuffer.cs
  4. 8
      src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs
  5. 96
      src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
  6. 4
      src/Avalonia.Native/AvaloniaNativeTextInputMethod.cs
  7. 8
      src/Browser/Avalonia.Browser/BrowserTextInputMethod.cs
  8. 2
      src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs
  9. 12
      src/iOS/Avalonia.iOS/TextInputResponder.cs

2
src/Android/Avalonia.Android/Platform/Input/AndroidInputMethod.cs

@ -126,7 +126,7 @@ namespace Avalonia.Android.Platform.Input
_inputConnection.IsInUpdate = true;
var selection = Client.ActualSelection;
var selection = Client.Selection;
var composition = _inputConnection.EditBuffer.HasComposition ? _inputConnection.EditBuffer.Composition!.Value : new TextSelection(-1,-1);

2
src/Android/Avalonia.Android/Platform/Input/AvaloniaInputConnection.cs

@ -279,7 +279,7 @@ namespace Avalonia.Android.Platform.Input
public ICharSequence? GetSelectedTextFormatted([GeneratedEnum] GetTextFlags flags)
{
return new Java.Lang.String(_editBuffer.SelectedText ?? "");
return _editBuffer.SelectedText;
}
public ICharSequence? GetTextAfterCursorFormatted(int n, [GeneratedEnum] GetTextFlags flags)

29
src/Android/Avalonia.Android/Platform/Input/TextEditBuffer.cs

@ -24,14 +24,14 @@ namespace Avalonia.Android.Platform.Input
{
get
{
var selection = _textInputMethod.Client?.ActualSelection ?? default;
var selection = _textInputMethod.Client?.Selection ?? default;
return new TextSelection(Math.Min(selection.Start, selection.End), Math.Max(selection.Start, selection.End));
}
set
{
if (_textInputMethod.Client is { } client)
client.ActualSelection = value;
client.Selection = value;
}
}
@ -51,18 +51,18 @@ namespace Avalonia.Android.Platform.Input
}
}
public string? SelectedText
public Java.Lang.String? SelectedText
{
get
{
if(_textInputMethod.Client is not { } client || Selection.Start < 0 || Selection.End >= client.Text.Length)
{
return string.Empty;
return new Java.Lang.String();
}
var selection = Selection;
return Text.Substring(selection.Start, selection.End - selection.Start);
return new Java.Lang.String(Text.Substring(selection.Start, selection.End - selection.Start));
}
}
@ -105,14 +105,23 @@ namespace Avalonia.Android.Platform.Input
}
}
internal Java.Lang.ICharSequence? GetTextBeforeCursor(int n) => new Java.Lang.String(_textInputMethod.Client?.GetTextBeforeCaret(n) ?? string.Empty);
internal Java.Lang.ICharSequence? GetTextAfterCursor(int n) => new Java.Lang.String(_textInputMethod.Client?.GetTextAfterCaret(n) ?? string.Empty);
internal Java.Lang.ICharSequence? GetTextBeforeCursor(int n)
{
var start = Math.Max(Selection.Start - n, 0);
var length = Math.Min(n, Selection.Start);
return new Java.Lang.String(_textInputMethod.Client?.GetTextInRange(start, length).ToArray() ?? Array.Empty<char>());
}
internal Java.Lang.ICharSequence? GetTextAfterCursor(int n)
{
return new Java.Lang.String(_textInputMethod.Client?.GetTextInRange(Selection.End, n).ToArray() ?? Array.Empty<char>());
}
internal void Remove(int index, int length)
{
if (_textInputMethod.Client is { } client)
{
client.ActualSelection = new TextSelection(index, index + length);
client.Selection = new TextSelection(index, index + length);
if (length > 0)
_textInputMethod?.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
}
@ -126,12 +135,12 @@ namespace Avalonia.Android.Platform.Input
var realEnd = Math.Max(start, end);
if (realEnd > realStart)
{
client.ActualSelection = new TextSelection(realStart, realEnd);
client.Selection = new TextSelection(realStart, realEnd);
_textInputMethod?.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
}
_topLevel.TextInput(text);
var index = realStart + text.Length;
client.ActualSelection = new TextSelection(index, index);
client.Selection = new TextSelection(index, index);
Composition = null;
}
}

8
src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs

@ -1,4 +1,5 @@
using System;
using Avalonia.Media.TextFormatting;
namespace Avalonia.Input.TextInput
{
@ -64,7 +65,7 @@ namespace Avalonia.Input.TextInput
/// <summary>
/// Gets or sets the curent selection range within current surrounding text.
/// </summary>
public abstract TextSelection Selection { get; set; }
public abstract TextSelection SelectionInSurroundingText { get; set; }
/// <summary>
/// Sets the non-committed input string
@ -122,10 +123,9 @@ namespace Avalonia.Input.TextInput
ResetRequested?.Invoke(this, EventArgs.Empty);
}
internal virtual string GetTextBeforeCaret(int length) => string.Empty;
internal virtual string GetTextAfterCaret(int length) => string.Empty;
internal virtual ReadOnlySpan<char> GetTextInRange(int start, int length) => Span<char>.Empty;
internal virtual TextSelection ActualSelection { get => Selection; set => Selection = value; }
internal virtual TextSelection Selection { get => SelectionInSurroundingText; set => SelectionInSurroundingText = value; }
}
public record struct TextSelection(int Start, int End);

96
src/Avalonia.Controls/TextBoxTextInputMethodClient.cs

@ -48,97 +48,15 @@ namespace Avalonia.Controls
internal override string Text => _presenter?.Text ?? string.Empty;
internal override string GetTextBeforeCaret(int length)
internal override ReadOnlySpan<char> GetTextInRange(int start, int length)
{
if (_presenter is null || _parent is null)
{
return "";
return base.GetTextInRange(start, length);
}
var selectionStart = _presenter.SelectionStart;
var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(selectionStart, false);
var textLine = _presenter.TextLayout.TextLines[lineIndex];
var offset = selectionStart - textLine.FirstTextSourceIndex;
var currentLineLength = Math.Min(offset, length);
var start = Math.Max(offset - currentLineLength, 0);
var lineText = GetTextLineText(textLine);
var text = lineText.Substring(start, currentLineLength);
var newText = text;
length -= currentLineLength;
while (length > 0)
{
lineIndex--;
if (lineIndex >= 0)
{
textLine = _presenter.TextLayout.TextLines[lineIndex];
currentLineLength = Math.Min(textLine.Length, length);
lineText = GetTextLineText(textLine);
text = lineText.Substring(textLine.Length - currentLineLength, currentLineLength);
newText = text + newText;
length -= currentLineLength;
}
else
break;
}
return newText;
}
internal override string GetTextAfterCaret(int length)
{
if (_presenter is null || _parent is null)
{
return "";
}
var selectionEnd = _presenter.SelectionStart;
var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(selectionEnd, false);
var textLine = _presenter.TextLayout.TextLines[lineIndex];
var lastIndex = textLine.FirstTextSourceIndex + textLine.Length;
var currentLineLength = Math.Min(lastIndex - selectionEnd, length);
var start = Math.Max(selectionEnd - textLine.FirstTextSourceIndex, 0);
var builder = new StringBuilder();
var lineText = GetTextLineText(textLine);
builder.Append(lineText.Substring(start, currentLineLength));
length -= currentLineLength;
while (length > 0)
{
lineIndex++;
if (lineIndex < _presenter.TextLayout.TextLines.Count)
{
textLine = _presenter.TextLayout.TextLines[lineIndex];
currentLineLength = Math.Min(textLine.Length, length);
lineText = GetTextLineText(textLine);
var text = lineText.Substring(0, currentLineLength);
builder.Append(text);
length -= currentLineLength;
}
else
break;
}
return builder.ToString();
var end = Math.Max(0 ,Math.Min(start + length, Text.Length - 1));
start = Math.Max(start, 0);
return Text.AsSpan().Slice(start, end - start);
}
public override Rect CursorRectangle
@ -161,7 +79,7 @@ namespace Avalonia.Controls
}
}
public override TextSelection Selection
public override TextSelection SelectionInSurroundingText
{
get
{
@ -205,7 +123,7 @@ namespace Avalonia.Controls
}
}
internal override TextSelection ActualSelection
internal override TextSelection Selection
{
get
{

4
src/Avalonia.Native/AvaloniaNativeTextInputMethod.cs

@ -97,7 +97,7 @@ namespace Avalonia.Native
}
var surroundingText = _client.SurroundingText;
var selection = _client.Selection;
var selection = _client.SelectionInSurroundingText;
_inputMethod.SetSurroundingText(
surroundingText ?? "",
@ -137,7 +137,7 @@ namespace Avalonia.Native
{
if (_client.SupportsSurroundingText)
{
_client.Selection = new TextSelection(start, end);
_client.SelectionInSurroundingText = new TextSelection(start, end);
}
}
}

8
src/Browser/Avalonia.Browser/BrowserTextInputMethod.cs

@ -47,7 +47,7 @@ internal class BrowserTextInputMethod(
ShowIme();
var surroundingText = _client.SurroundingText ?? "";
var selection = _client.Selection;
var selection = _client.SelectionInSurroundingText;
InputHelper.SetSurroundingText(_inputElement, surroundingText, selection.Start, selection.End);
}
@ -76,7 +76,7 @@ internal class BrowserTextInputMethod(
if (_client != null)
{
var surroundingText = _client.SurroundingText ?? "";
var selection = _client.Selection;
var selection = _client.SelectionInSurroundingText;
InputHelper.SetSurroundingText(_inputElement, surroundingText, selection.Start, selection.End);
}
@ -86,7 +86,7 @@ internal class BrowserTextInputMethod(
{
InputHelper.FocusElement(_inputElement);
InputHelper.SetBounds(_inputElement, (int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height,
_client?.Selection.End ?? 0);
_client?.SelectionInSurroundingText.End ?? 0);
InputHelper.FocusElement(_inputElement);
}
@ -118,7 +118,7 @@ internal class BrowserTextInputMethod(
if (start != -1 && end != -1 && _client != null)
{
_client.Selection = new TextSelection(start, end);
_client.SelectionInSurroundingText = new TextSelection(start, end);
}
}

2
src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs

@ -298,7 +298,7 @@ namespace Avalonia.Win32.Input
{
Client.SetPreeditText(null);
if (Client.SupportsSurroundingText && Client.Selection.Start != Client.Selection.End)
if (Client.SupportsSurroundingText && Client.SelectionInSurroundingText.Start != Client.SelectionInSurroundingText.End)
{
KeyPress(Key.Delete, PhysicalKey.Delete);
}

12
src/iOS/Avalonia.iOS/TextInputResponder.cs

@ -231,7 +231,7 @@ partial class AvaloniaView
}
else
{
var currentSelection = _client.Selection;
var currentSelection = _client.SelectionInSurroundingText;
var span = new CombinedSpan3<char>(surroundingText.AsSpan(0, currentSelection.Start),
_markedText,
surroundingText.AsSpan(currentSelection.Start, currentSelection.End - currentSelection.Start));
@ -249,7 +249,7 @@ partial class AvaloniaView
var r = (AvaloniaTextRange)range;
Logger.TryGet(LogEventLevel.Debug, ImeLog)?
.Log(null, "IUIKeyInput.ReplaceText {start} {end} {text}", r.StartIndex, r.EndIndex, text);
_client.Selection = new TextSelection(r.StartIndex, r.EndIndex);
_client.SelectionInSurroundingText = new TextSelection(r.StartIndex, r.EndIndex);
TextInput(text);
}
@ -470,19 +470,19 @@ partial class AvaloniaView
{
get
{
return new AvaloniaTextRange(_client.Selection.Start, _client.Selection.End);
return new AvaloniaTextRange(_client.SelectionInSurroundingText.Start, _client.SelectionInSurroundingText.End);
}
set
{
if (_inSurroundingTextUpdateEvent > 0)
return;
if (value == null)
_client.Selection = default;
_client.SelectionInSurroundingText = default;
else
{
var r = (AvaloniaTextRange)value;
_client.Selection = new TextSelection(r.StartIndex, r.EndIndex);
_client.SelectionInSurroundingText = new TextSelection(r.StartIndex, r.EndIndex);
}
}
}
@ -504,7 +504,7 @@ partial class AvaloniaView
{
if (string.IsNullOrWhiteSpace(_markedText))
return null!;
return new AvaloniaTextRange(_client.Selection.Start, _client.Selection.Start + _markedText.Length);
return new AvaloniaTextRange(_client.SelectionInSurroundingText.Start, _client.SelectionInSurroundingText.Start + _markedText.Length);
}
}

Loading…
Cancel
Save