Browse Source

wip

android_text_client
Emmanuel Hansen 5 months ago
parent
commit
6afad040ea
  1. 2
      src/Android/Avalonia.Android/Platform/Input/AndroidInputMethod.cs
  2. 7
      src/Android/Avalonia.Android/Platform/Input/AvaloniaInputConnection.cs
  3. 41
      src/Android/Avalonia.Android/Platform/Input/EditCommand.cs
  4. 51
      src/Android/Avalonia.Android/Platform/Input/TextEditBuffer.cs
  5. 7
      src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs
  6. 73
      src/Avalonia.Controls/Primitives/TextSelectionCanvas.cs
  7. 120
      src/Avalonia.Controls/TextBoxTextInputMethodClient.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.Selection;
var selection = Client.ActualSelection;
var composition = _inputConnection.EditBuffer.HasComposition ? _inputConnection.EditBuffer.Composition!.Value : new TextSelection(-1,-1);

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

@ -284,15 +284,12 @@ namespace Avalonia.Android.Platform.Input
public ICharSequence? GetTextAfterCursorFormatted(int n, [GeneratedEnum] GetTextFlags flags)
{
var end = Math.Min(_editBuffer.Selection.End, _editBuffer.Text.Length);
return SafeSubstring(_editBuffer.Text, end, Math.Min(n, _editBuffer.Text.Length - end));
return _editBuffer.GetTextAfterCursor(n);
}
public ICharSequence? GetTextBeforeCursorFormatted(int n, [GeneratedEnum] GetTextFlags flags)
{
var start = Math.Max(0, _editBuffer.Selection.Start - n);
var length = _editBuffer.Selection.Start - start;
return SafeSubstring(_editBuffer.Text, start, length);
return _editBuffer.GetTextBeforeCursor(n);
}
public bool PerformPrivateCommand(string? action, Bundle? data)

41
src/Android/Avalonia.Android/Platform/Input/EditCommand.cs

@ -21,9 +21,7 @@ namespace Avalonia.Android.Platform.Input
public override void Apply(TextEditBuffer buffer)
{
var start = Math.Clamp(_start, 0, buffer.Text.Length);
var end = Math.Clamp(_end, 0, buffer.Text.Length);
buffer.Selection = new TextSelection(start, end);
buffer.Selection = new TextSelection(_start, _end);
}
}
@ -57,11 +55,13 @@ namespace Avalonia.Android.Platform.Input
public override void Apply(TextEditBuffer buffer)
{
var end = Math.Min(buffer.Text.Length, buffer.Selection.End + _after);
var endCount = end - buffer.Selection.End;
var start = Math.Max(0, buffer.Selection.Start - _before);
buffer.Remove(buffer.Selection.End, endCount);
buffer.Remove(start, buffer.Selection.Start - start);
var length = buffer.Text.Length;
var selection = buffer.Selection;
var end = Math.Min(length, selection.End + _after);
var endCount = end - selection.End;
var start = Math.Max(0, selection.Start - _before);
buffer.Remove(selection.End, endCount);
buffer.Remove(start, selection.Start - start);
buffer.Selection = new TextSelection(start, start);
}
}
@ -81,13 +81,15 @@ namespace Avalonia.Android.Platform.Input
{
var beforeLengthInChar = 0;
var selection = buffer.Selection;
var text = buffer.Text;
for (int i = 0; i < _before; i++)
{
beforeLengthInChar++;
if (buffer.Selection.Start > beforeLengthInChar)
if (selection.Start > beforeLengthInChar)
{
var lead = buffer.Text[buffer.Selection.Start - beforeLengthInChar - 1];
var trail = buffer.Text[buffer.Selection.Start - beforeLengthInChar];
var lead = text[selection.Start - beforeLengthInChar - 1];
var trail = text[selection.Start - beforeLengthInChar];
if (char.IsSurrogatePair(lead, trail))
{
@ -95,7 +97,7 @@ namespace Avalonia.Android.Platform.Input
}
}
if (beforeLengthInChar == buffer.Selection.Start)
if (beforeLengthInChar == selection.Start)
break;
}
@ -103,10 +105,10 @@ namespace Avalonia.Android.Platform.Input
for (int i = 0; i < _after; i++)
{
afterLengthInChar++;
if (buffer.Selection.End > afterLengthInChar)
if (selection.End > afterLengthInChar)
{
var lead = buffer.Text[buffer.Selection.End + afterLengthInChar - 1];
var trail = buffer.Text[buffer.Selection.End + afterLengthInChar];
var lead = text[selection.End + afterLengthInChar - 1];
var trail = text[selection.End + afterLengthInChar];
if (char.IsSurrogatePair(lead, trail))
{
@ -114,12 +116,12 @@ namespace Avalonia.Android.Platform.Input
}
}
if (buffer.Selection.End + afterLengthInChar == buffer.Text.Length)
if (selection.End + afterLengthInChar == text.Length)
break;
}
var start = buffer.Selection.Start - beforeLengthInChar;
buffer.Remove(buffer.Selection.End, afterLengthInChar);
var start = selection.Start - beforeLengthInChar;
buffer.Remove(selection.End, afterLengthInChar);
buffer.Remove(start, beforeLengthInChar);
buffer.Selection = new TextSelection(start, start);
}
@ -139,7 +141,8 @@ namespace Avalonia.Android.Platform.Input
public override void Apply(TextEditBuffer buffer)
{
buffer.ComposingText = _text;
var newCursor = _newCursorPosition > 0 ? buffer.Selection.Start + _newCursorPosition - 1 : buffer.Selection.Start + _newCursorPosition;
var selection = buffer.Selection;
var newCursor = _newCursorPosition > 0 ? selection.Start + _newCursorPosition - 1 : selection.Start + _newCursorPosition;
buffer.Selection = new TextSelection(newCursor, newCursor);
}
}

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

@ -1,5 +1,4 @@
using System;
using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.Platform.SkiaPlatform;
@ -25,14 +24,14 @@ namespace Avalonia.Android.Platform.Input
{
get
{
var selection = _textInputMethod.Client?.Selection ?? default;
var selection = _textInputMethod.Client?.ActualSelection ?? default;
return new TextSelection(Math.Min(selection.Start, selection.End), Math.Max(selection.Start, selection.End));
}
set
{
if (_textInputMethod.Client is { } client)
client.Selection = value;
client.ActualSelection = value;
}
}
@ -56,14 +55,14 @@ namespace Avalonia.Android.Platform.Input
{
get
{
if(_textInputMethod.Client is not { } client || Selection.Start < 0 || Selection.End >= client.SurroundingText.Length)
if(_textInputMethod.Client is not { } client || Selection.Start < 0 || Selection.End >= client.Text.Length)
{
return "";
return string.Empty;
}
var selection = Selection;
return client.SurroundingText.Substring(selection.Start, selection.End - selection.Start);
return Text.Substring(selection.Start, selection.End - selection.Start);
}
}
@ -74,36 +73,46 @@ namespace Avalonia.Android.Platform.Input
if (HasComposition)
{
var start = Composition!.Value.Start;
Replace(Composition!.Value.Start, Composition!.Value.End, value ?? "");
Replace(Composition!.Value.Start, Composition!.Value.End, value ?? string.Empty);
Composition = new TextSelection(start, start + (value?.Length ?? 0));
}
else
{
var selection = Selection;
Replace(selection.Start, selection.End, value ?? "");
Replace(selection.Start, selection.End, value ?? string.Empty);
Composition = new TextSelection(selection.Start, selection.Start + (value?.Length ?? 0));
}
}
}
public string Text => _textInputMethod.Client?.SurroundingText ?? "";
public string Text => _textInputMethod.Client?.Text ?? string.Empty;
public ExtractedText? ExtractedText => new ExtractedText
public ExtractedText? ExtractedText
{
Flags = Text.Contains('\n') ? 0 : ExtractedTextFlags.SingleLine,
PartialStartOffset = -1,
PartialEndOffset = Text.Length,
SelectionStart = Selection.Start,
SelectionEnd = Selection.End,
StartOffset = 0,
Text = new Java.Lang.String(Text)
};
get
{
var text = Text;
return new ExtractedText
{
Flags = text.Contains('\n') ? 0 : ExtractedTextFlags.SingleLine,
PartialStartOffset = -1,
PartialEndOffset = text.Length,
SelectionStart = Selection.Start,
SelectionEnd = Selection.End,
StartOffset = 0,
Text = new Java.Lang.String(text)
};
}
}
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 void Remove(int index, int length)
{
if (_textInputMethod.Client is { } client)
{
client.Selection = new TextSelection(index, index + length);
client.ActualSelection = new TextSelection(index, index + length);
if (length > 0)
_textInputMethod?.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
}
@ -117,12 +126,12 @@ namespace Avalonia.Android.Platform.Input
var realEnd = Math.Max(start, end);
if (realEnd > realStart)
{
client.Selection = new TextSelection(realStart, realEnd);
client.ActualSelection = new TextSelection(realStart, realEnd);
_textInputMethod?.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
}
_topLevel.TextInput(text);
var index = realStart + text.Length;
client.Selection = new TextSelection(index, index);
client.ActualSelection = new TextSelection(index, index);
Composition = null;
}
}

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

@ -54,6 +54,8 @@ namespace Avalonia.Input.TextInput
/// </summary>
public abstract string SurroundingText { get; }
internal virtual string Text => SurroundingText;
/// <summary>
/// Gets the cursor rectangle relative to the TextViewVisual
/// </summary>
@ -119,6 +121,11 @@ 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 TextSelection ActualSelection { get => Selection; set => Selection = value; }
}
public record struct TextSelection(int Start, int End);

73
src/Avalonia.Controls/Primitives/TextSelectionCanvas.cs

@ -177,22 +177,30 @@ namespace Avalonia.Controls.Primitives
var selectionEnd = _textBox.SelectionEnd;
var start = Math.Min(selectionStart, selectionEnd);
var length = Math.Max(selectionStart, selectionEnd) - start;
var rects = new List<Rect>(_presenter.TextLayout.HitTestTextRange(start, length));
if (rects.Count > 0)
try
{
var first = rects[0];
var last = rects[rects.Count -1];
var rects = new List<Rect>(_presenter.TextLayout.HitTestTextRange(start, length));
if (handle.SelectionHandleType == SelectionHandleType.Start)
handle?.SetTopLeft(ToLayer(first.BottomLeft));
else
handle?.SetTopLeft(ToLayer(last.BottomRight));
if (rects.Count > 0)
{
var first = rects[0];
var last = rects[rects.Count - 1];
if (handle.SelectionHandleType == SelectionHandleType.Start)
handle?.SetTopLeft(ToLayer(first.BottomLeft));
else
handle?.SetTopLeft(ToLayer(last.BottomRight));
if (otherHandle.SelectionHandleType == SelectionHandleType.Start)
otherHandle?.SetTopLeft(ToLayer(first.BottomLeft));
else
otherHandle?.SetTopLeft(ToLayer(last.BottomRight));
}
}
catch(InvalidOperationException)
{
if (otherHandle.SelectionHandleType == SelectionHandleType.Start)
otherHandle?.SetTopLeft(ToLayer(first.BottomLeft));
else
otherHandle?.SetTopLeft(ToLayer(last.BottomRight));
}
_presenter?.MoveCaretToTextPosition(position);
@ -241,29 +249,36 @@ namespace Avalonia.Controls.Primitives
var start = Math.Min(selectionStart, selectionEnd);
var length = Math.Max(selectionStart, selectionEnd) - start;
var rects = new List<Rect>(_presenter.TextLayout.HitTestTextRange(start, length));
if (rects.Count > 0)
try
{
var first = rects[0];
var last = rects[rects.Count - 1];
var rects = new List<Rect>(_presenter.TextLayout.HitTestTextRange(start, length));
if (!_startHandle.IsDragging)
if (rects.Count > 0)
{
_startHandle.SetTopLeft(ToLayer(first.BottomLeft));
_startHandle.SelectionHandleType = selectionStart < selectionEnd ?
SelectionHandleType.Start :
SelectionHandleType.End;
}
var first = rects[0];
var last = rects[rects.Count - 1];
if (!_endHandle.IsDragging)
{
_endHandle.SetTopLeft(ToLayer(last.BottomRight));
_endHandle.SelectionHandleType = selectionStart > selectionEnd ?
SelectionHandleType.Start :
SelectionHandleType.End;
if (!_startHandle.IsDragging)
{
_startHandle.SetTopLeft(ToLayer(first.BottomLeft));
_startHandle.SelectionHandleType = selectionStart < selectionEnd ?
SelectionHandleType.Start :
SelectionHandleType.End;
}
if (!_endHandle.IsDragging)
{
_endHandle.SetTopLeft(ToLayer(last.BottomRight));
_endHandle.SelectionHandleType = selectionStart > selectionEnd ?
SelectionHandleType.Start :
SelectionHandleType.End;
}
}
}
catch (InvalidOperationException)
{
}
}
}

120
src/Avalonia.Controls/TextBoxTextInputMethodClient.cs

@ -1,4 +1,5 @@
using System;
using System.Text;
using Avalonia.Controls.Presenters;
using Avalonia.Input.TextInput;
using Avalonia.Media.TextFormatting;
@ -45,6 +46,101 @@ namespace Avalonia.Controls
}
}
internal override string Text => _presenter?.Text ?? string.Empty;
internal override string GetTextBeforeCaret(int length)
{
if (_presenter is null || _parent is null)
{
return "";
}
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();
}
public override Rect CursorRectangle
{
get
@ -109,6 +205,30 @@ namespace Avalonia.Controls
}
}
internal override TextSelection ActualSelection
{
get
{
if (_presenter is null || _parent is null)
{
return default;
}
return new TextSelection(_presenter.SelectionStart, _presenter.SelectionEnd);
}
set
{
if (_parent is not null)
{
_parent.SelectionStart = value.Start;
_parent.SelectionEnd = value.End;
RaiseSelectionChanged();
}
}
}
public override bool SupportsPreedit => true;
public override bool SupportsSurroundingText => true;

Loading…
Cancel
Save