diff --git a/src/Android/Avalonia.Android/Platform/Input/AndroidInputMethod.cs b/src/Android/Avalonia.Android/Platform/Input/AndroidInputMethod.cs
index 2e8e145ef8..aa6bd5740b 100644
--- a/src/Android/Avalonia.Android/Platform/Input/AndroidInputMethod.cs
+++ b/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);
diff --git a/src/Android/Avalonia.Android/Platform/Input/AvaloniaInputConnection.cs b/src/Android/Avalonia.Android/Platform/Input/AvaloniaInputConnection.cs
index 39e1574901..3c7e13f8ef 100644
--- a/src/Android/Avalonia.Android/Platform/Input/AvaloniaInputConnection.cs
+++ b/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)
diff --git a/src/Android/Avalonia.Android/Platform/Input/EditCommand.cs b/src/Android/Avalonia.Android/Platform/Input/EditCommand.cs
index c110807dd9..5992a22dbb 100644
--- a/src/Android/Avalonia.Android/Platform/Input/EditCommand.cs
+++ b/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);
}
}
diff --git a/src/Android/Avalonia.Android/Platform/Input/TextEditBuffer.cs b/src/Android/Avalonia.Android/Platform/Input/TextEditBuffer.cs
index e040687d6d..1490fdffd3 100644
--- a/src/Android/Avalonia.Android/Platform/Input/TextEditBuffer.cs
+++ b/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;
}
}
diff --git a/src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs b/src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs
index 7f9870315b..5a07e49ba7 100644
--- a/src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs
+++ b/src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs
@@ -54,6 +54,8 @@ namespace Avalonia.Input.TextInput
///
public abstract string SurroundingText { get; }
+ internal virtual string Text => SurroundingText;
+
///
/// Gets the cursor rectangle relative to the TextViewVisual
///
@@ -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);
diff --git a/src/Avalonia.Controls/Primitives/TextSelectionCanvas.cs b/src/Avalonia.Controls/Primitives/TextSelectionCanvas.cs
index 4248ea65a1..a592cf77cf 100644
--- a/src/Avalonia.Controls/Primitives/TextSelectionCanvas.cs
+++ b/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(_presenter.TextLayout.HitTestTextRange(start, length));
- if (rects.Count > 0)
+ try
{
- var first = rects[0];
- var last = rects[rects.Count -1];
+ var rects = new List(_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(_presenter.TextLayout.HitTestTextRange(start, length));
-
- if (rects.Count > 0)
+ try
{
- var first = rects[0];
- var last = rects[rects.Count - 1];
+ var rects = new List(_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)
+ {
+
+ }
}
}
diff --git a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
index 12e8e97640..d332ee2b30 100644
--- a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
+++ b/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;