diff --git a/samples/MobileSandbox.Android/MainActivity.cs b/samples/MobileSandbox.Android/MainActivity.cs
index 807afb0bd0..702e9be9e6 100644
--- a/samples/MobileSandbox.Android/MainActivity.cs
+++ b/samples/MobileSandbox.Android/MainActivity.cs
@@ -1,5 +1,8 @@
-using Android.App;
+using System;
+using Android.App;
using Android.Content.PM;
+using Android.OS;
+using Avalonia;
using Avalonia.Android;
namespace MobileSandbox.Android
diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs
index 2bf3486ca5..ea1958efa6 100644
--- a/src/Android/Avalonia.Android/AndroidInputMethod.cs
+++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs
@@ -1,10 +1,10 @@
using System;
using Android.Content;
using Android.Runtime;
+using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.Platform.SkiaPlatform;
-using Avalonia.Controls.Presenters;
using Avalonia.Input.TextInput;
namespace Avalonia.Android
@@ -13,7 +13,7 @@ namespace Avalonia.Android
{
public View View { get; }
- public ITextInputMethodClient Client { get; }
+ public TextInputMethodClient Client { get; }
public bool IsActive { get; }
@@ -36,7 +36,7 @@ namespace Avalonia.Android
{
private readonly TView _host;
private readonly InputMethodManager _imm;
- private ITextInputMethodClient _client;
+ private TextInputMethodClient _client;
private AvaloniaInputConnection _inputConnection;
public AndroidInputMethod(TView host)
@@ -56,7 +56,7 @@ namespace Avalonia.Android
public bool IsActive => Client != null;
- public ITextInputMethodClient Client => _client;
+ public TextInputMethodClient Client => _client;
public InputMethodManager IMM => _imm;
@@ -65,7 +65,7 @@ namespace Avalonia.Android
}
- public void SetClient(ITextInputMethodClient client)
+ public void SetClient(TextInputMethodClient client)
{
_client = client;
@@ -77,9 +77,24 @@ namespace Avalonia.Android
_imm.ShowSoftInput(_host, ShowFlags.Implicit);
- var surroundingText = Client.SurroundingText;
+ var selection = Client.Selection;
- _imm.UpdateSelection(_host, surroundingText.AnchorOffset, surroundingText.CursorOffset, surroundingText.AnchorOffset, surroundingText.CursorOffset);
+ _imm.UpdateSelection(_host, selection.Start, selection.End, selection.Start, selection.End);
+
+ var surroundingText = _client.SurroundingText ?? "";
+
+ var extractedText = new ExtractedText
+ {
+ Text = new Java.Lang.String(surroundingText),
+ SelectionStart = selection.Start,
+ SelectionEnd = selection.End,
+ PartialEndOffset = surroundingText.Length
+ };
+
+ _imm.UpdateExtractedText(_host, _inputConnection?.ExtractedTextToken ?? 0, extractedText);
+
+ _client.SurroundingTextChanged += _client_SurroundingTextChanged;
+ _client.SelectionChanged += _client_SelectionChanged;
}
else
{
@@ -87,6 +102,30 @@ namespace Avalonia.Android
}
}
+ private void _client_SelectionChanged(object sender, EventArgs e)
+ {
+ var selection = Client.Selection;
+
+ _imm.UpdateSelection(_host, selection.Start, selection.End, selection.Start, selection.End);
+
+ _inputConnection.SetSelection(selection.Start, selection.End);
+ }
+
+ private void _client_SurroundingTextChanged(object sender, EventArgs e)
+ {
+ var surroundingText = _client.SurroundingText ?? "";
+
+ _inputConnection.EditableWrapper.IgnoreChange = true;
+
+ _inputConnection.Editable.Replace(0, _inputConnection.Editable.Length(), surroundingText);
+
+ _inputConnection.EditableWrapper.IgnoreChange = false;
+
+ var selection = Client.Selection;
+
+ _imm.UpdateSelection(_host, selection.Start, selection.End, selection.Start, selection.End);
+ }
+
public void SetCursorRect(Rect rect)
{
@@ -134,25 +173,8 @@ namespace Avalonia.Android
outAttrs.ImeOptions |= ImeFlags.NoFullscreen | ImeFlags.NoExtractUi;
- _client.TextEditable = _inputConnection.InputEditable;
-
return _inputConnection;
});
}
}
-
- internal readonly record struct ComposingRegion
- {
- private readonly int _start = -1;
- private readonly int _end = -1;
-
- public ComposingRegion(int start, int end)
- {
- _start = start;
- _end = end;
- }
-
- public int Start => _start;
- public int End => _end;
- }
}
diff --git a/src/Android/Avalonia.Android/InputEditable.cs b/src/Android/Avalonia.Android/InputEditable.cs
deleted file mode 100644
index c5b68d2652..0000000000
--- a/src/Android/Avalonia.Android/InputEditable.cs
+++ /dev/null
@@ -1,127 +0,0 @@
-using System;
-using Android.Runtime;
-using Android.Text;
-using Android.Views;
-using Android.Views.InputMethods;
-using Avalonia.Android.Platform.SkiaPlatform;
-using Avalonia.Controls.Presenters;
-using Avalonia.Input;
-using Avalonia.Input.Raw;
-using Avalonia.Input.TextInput;
-using Java.Lang;
-using static System.Net.Mime.MediaTypeNames;
-
-namespace Avalonia.Android
-{
- internal class InputEditable : SpannableStringBuilder, ITextEditable
- {
- private readonly TopLevelImpl _topLevel;
- private readonly IAndroidInputMethod _inputMethod;
- private readonly AvaloniaInputConnection _avaloniaInputConnection;
- private int _currentBatchLevel;
- private string _previousText;
- private int _previousSelectionStart;
- private int _previousSelectionEnd;
-
- public event EventHandler TextChanged;
- public event EventHandler SelectionChanged;
- public event EventHandler CompositionChanged;
-
- public InputEditable(TopLevelImpl topLevel, IAndroidInputMethod inputMethod, AvaloniaInputConnection avaloniaInputConnection)
- {
- _topLevel = topLevel;
- _inputMethod = inputMethod;
- _avaloniaInputConnection = avaloniaInputConnection;
- }
-
- public InputEditable(ICharSequence text) : base(text)
- {
- }
-
- public InputEditable(string text) : base(text)
- {
- }
-
- public InputEditable(ICharSequence text, int start, int end) : base(text, start, end)
- {
- }
-
- public InputEditable(string text, int start, int end) : base(text, start, end)
- {
- }
-
- protected InputEditable(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
- {
- }
-
- public int SelectionStart
- {
- get => Selection.GetSelectionStart(this); set
- {
- var end = SelectionEnd < 0 ? 0 : SelectionEnd;
- _avaloniaInputConnection.SetSelection(value, end);
- _inputMethod.IMM.UpdateSelection(_topLevel.View, value, end, value, end);
- }
- }
- public int SelectionEnd
- {
- get => Selection.GetSelectionEnd(this); set
- {
- var start = SelectionStart < 0 ? 0 : SelectionStart;
- _avaloniaInputConnection.SetSelection(start, value);
- _inputMethod.IMM.UpdateSelection(_topLevel.View, start, value, start, value);
- }
- }
-
- public string? Text
- {
- get => ToString(); set
- {
- if (Text != value)
- {
- Clear();
- Insert(0, value ?? "");
- }
- }
- }
-
- public int CompositionStart => BaseInputConnection.GetComposingSpanStart(this);
-
- public int CompositionEnd => BaseInputConnection.GetComposingSpanEnd(this);
-
- public void BeginBatchEdit()
- {
- _currentBatchLevel++;
-
- if (_currentBatchLevel == 1)
- {
- _previousText = ToString();
- _previousSelectionStart = SelectionStart;
- _previousSelectionEnd = SelectionEnd;
- }
- }
-
- public void EndBatchEdit()
- {
- if (_currentBatchLevel == 1)
- {
- if(_previousText != Text)
- {
- TextChanged?.Invoke(this, EventArgs.Empty);
- }
-
- if (_previousSelectionStart != SelectionStart || _previousSelectionEnd != SelectionEnd)
- {
- SelectionChanged?.Invoke(this, EventArgs.Empty);
- }
- }
-
- _currentBatchLevel--;
- }
-
- public void RaiseCompositionChanged()
- {
- CompositionChanged?.Invoke(this, EventArgs.Empty);
- }
- }
-}
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index a29f06a106..5d82d099c9 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -11,6 +11,7 @@ using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using AndroidX.AppCompat.App;
+using Avalonia.Android.Platform.Input;
using Avalonia.Android.Platform.Specific;
using Avalonia.Android.Platform.Specific.Helpers;
using Avalonia.Android.Platform.Storage;
@@ -28,6 +29,7 @@ using Avalonia.Platform.Storage;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Java.Lang;
+using static System.Net.Mime.MediaTypeNames;
using ClipboardManager = Android.Content.ClipboardManager;
namespace Avalonia.Android.Platform.SkiaPlatform
@@ -429,79 +431,170 @@ namespace Avalonia.Android.Platform.SkiaPlatform
activity.Window.Attributes = attr;
}
}
+
+ internal void TextInput(string text)
+ {
+ if(Input != null)
+ {
+ var args = new RawTextInputEventArgs(AndroidKeyboardDevice.Instance, (ulong)DateTime.Now.Ticks, InputRoot, text);
+
+ Input(args);
+ }
+ }
+ }
+
+ internal class EditableWrapper : SpannableStringBuilder
+ {
+ private readonly AvaloniaInputConnection _inputConnection;
+
+ public EditableWrapper(AvaloniaInputConnection inputConnection)
+ {
+ _inputConnection = inputConnection;
+ }
+
+ public bool IgnoreChange { get; set; }
+
+ public override IEditable Replace(int start, int end, ICharSequence tb)
+ {
+ if (!IgnoreChange && start != end)
+ {
+ var text = tb.SubSequence(0, tb.Length());
+
+ SelectSurroundingTextForDeletion(start, end);
+ }
+
+ return base.Replace(start, end, tb);
+ }
+
+ public override IEditable Replace(int start, int end, ICharSequence tb, int tbstart, int tbend)
+ {
+ if (!IgnoreChange && start != end)
+ {
+ var text = tb.SubSequence(tbstart, tbend);
+
+ SelectSurroundingTextForDeletion(start, end);
+ }
+
+ return base.Replace(start, end, tb, tbstart, tbend);
+ }
+
+ private void SelectSurroundingTextForDeletion(int start, int end)
+ {
+ _inputConnection.InputMethod.Client.Selection = new TextSelection(start, end);
+ }
}
internal class AvaloniaInputConnection : BaseInputConnection
{
- private readonly TopLevelImpl _topLevel;
+ private readonly TopLevelImpl _toplevel;
private readonly IAndroidInputMethod _inputMethod;
- private readonly InputEditable _editable;
+ private readonly EditableWrapper _editable;
+ private bool _commitInProgress;
+ private (int Start, int End)? _composingRegion;
+ private TextSelection _selection;
- public AvaloniaInputConnection(TopLevelImpl topLevel, IAndroidInputMethod inputMethod) : base(inputMethod.View, true)
+ public AvaloniaInputConnection(TopLevelImpl toplevel, IAndroidInputMethod inputMethod) : base(inputMethod.View, true)
{
- _topLevel = topLevel;
+ _toplevel = toplevel;
_inputMethod = inputMethod;
- _editable = new InputEditable(_topLevel, _inputMethod, this);
+ _editable = new EditableWrapper(this);
}
+ public int ExtractedTextToken { get; private set; }
+
public override IEditable Editable => _editable;
- internal InputEditable InputEditable => _editable;
+ public EditableWrapper EditableWrapper => _editable;
+
+ public IAndroidInputMethod InputMethod => _inputMethod;
+
+ public TopLevelImpl Toplevel => _toplevel;
public override bool SetComposingRegion(int start, int end)
{
- var ret = base.SetComposingRegion(start, end);
-
- InputEditable.RaiseCompositionChanged();
+ _composingRegion = new(start, end);
- return ret;
+ return base.SetComposingRegion(start, end);
}
public override bool SetComposingText(ICharSequence text, int newCursorPosition)
{
- var composingText = text.ToString();
-
- if (string.IsNullOrEmpty(composingText))
+ if(_composingRegion != null)
{
- return CommitText(text, newCursorPosition);
+ // Select the composing region.
+ InputMethod.Client.Selection = new TextSelection(_composingRegion.Value.Start, _composingRegion.Value.End);
}
- else
- {
- var ret = base.SetComposingText(text, newCursorPosition);
+ var compositionText = text.SubSequence(0, text.Length());
- InputEditable.RaiseCompositionChanged();
+ if (_inputMethod.IsActive && !_commitInProgress)
+ {
+ if (string.IsNullOrEmpty(compositionText))
+ _inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
- return ret;
+ else
+ _toplevel.TextInput(compositionText);
}
+
+ return true;
}
- public override bool BeginBatchEdit()
+ public override bool CommitText(ICharSequence text, int newCursorPosition)
{
- _editable.BeginBatchEdit();
+ _commitInProgress = true;
- return base.BeginBatchEdit();
- }
+ var ret = base.CommitText(text, newCursorPosition);
- public override bool EndBatchEdit()
- {
- var ret = base.EndBatchEdit();
- _editable.EndBatchEdit();
+ var committedText = text.SubSequence(0, text.Length());
+
+ if (_inputMethod.IsActive && !string.IsNullOrEmpty(committedText))
+ {
+ if(_composingRegion != null)
+ {
+ _inputMethod.Client.Selection = new TextSelection(_composingRegion.Value.Start, _composingRegion.Value.End);
+ }
+
+ _toplevel.TextInput(committedText);
+
+ _composingRegion = null;
+ }
+
+ _commitInProgress = false;
return ret;
}
public override bool FinishComposingText()
{
- var ret = base.FinishComposingText();
- InputEditable.RaiseCompositionChanged();
- return ret;
+ _composingRegion = null;
+ return base.FinishComposingText();
}
- public override bool CommitText(ICharSequence text, int newCursorPosition)
+ public override bool DeleteSurroundingText(int beforeLength, int afterLength)
{
- var ret = base.CommitText(text, newCursorPosition);
- InputEditable.RaiseCompositionChanged();
- return ret;
+ if (InputMethod.IsActive)
+ {
+ EditableWrapper.IgnoreChange = true;
+ }
+ var result = base.DeleteSurroundingText(beforeLength, afterLength);
+
+ if (InputMethod.IsActive)
+ {
+ var selection = _selection;
+
+ InputMethod.Client.Selection = new TextSelection(selection.Start - beforeLength, selection.End + afterLength);
+
+ InputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
+
+ EditableWrapper.IgnoreChange = true;
+ }
+
+ return result;
+ }
+
+ public override bool SetSelection(int start, int end)
+ {
+ _selection = new TextSelection(start, end);
+ return base.SetSelection(start, end);
}
public override bool PerformEditorAction([GeneratedEnum] ImeAction actionCode)
@@ -517,5 +610,48 @@ namespace Avalonia.Android.Platform.SkiaPlatform
return base.PerformEditorAction(actionCode);
}
+
+ public override ExtractedText GetExtractedText(ExtractedTextRequest request, [GeneratedEnum] GetTextFlags flags)
+ {
+ if (request == null)
+ return null;
+
+ ExtractedTextToken = request.Token;
+
+ var editable = Editable;
+
+ if (editable == null)
+ {
+ return null;
+ }
+
+ if (!_inputMethod.IsActive)
+ {
+ return null;
+ }
+
+ var selection = _selection;
+
+ ExtractedText extract = new ExtractedText
+ {
+ Flags = 0,
+ PartialStartOffset = -1,
+ PartialEndOffset = -1,
+ SelectionStart = selection.Start,
+ SelectionEnd = selection.End,
+ StartOffset = 0
+ };
+
+ if ((request.Flags & GetTextFlags.WithStyles) != 0)
+ {
+ extract.Text = new SpannableString(editable);
+ }
+ else
+ {
+ extract.Text = editable;
+ }
+
+ return extract;
+ }
}
}
diff --git a/src/Avalonia.Base/Input/TextInput/ITextEditable.cs b/src/Avalonia.Base/Input/TextInput/ITextEditable.cs
deleted file mode 100644
index f9f5ec8d40..0000000000
--- a/src/Avalonia.Base/Input/TextInput/ITextEditable.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Avalonia.Metadata;
-
-namespace Avalonia.Input.TextInput
-{
- [NotClientImplementable]
- public interface ITextEditable
- {
- event EventHandler TextChanged;
- event EventHandler SelectionChanged;
- event EventHandler CompositionChanged;
- int SelectionStart { get; set; }
- int SelectionEnd { get; set; }
- int CompositionStart { get; }
- int CompositionEnd { get; }
-
- string? Text { get; set; }
- }
-}
diff --git a/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs b/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs
deleted file mode 100644
index 2cdcd33626..0000000000
--- a/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-using System;
-using Avalonia.Media.TextFormatting;
-using Avalonia.VisualTree;
-
-namespace Avalonia.Input.TextInput
-{
- public interface ITextInputMethodClient
- {
- ///
- /// The cursor rectangle relative to the TextViewVisual
- ///
- Rect CursorRectangle { get; }
- ///
- /// Should be fired when cursor rectangle is changed inside the TextViewVisual
- ///
- event EventHandler? CursorRectangleChanged;
- ///
- /// The visual that's showing the text
- ///
- Visual TextViewVisual { get; }
- ///
- /// Should be fired when text-hosting visual is changed
- ///
- event EventHandler? TextViewVisualChanged;
- ///
- /// Indicates if TextViewVisual is capable of displaying non-committed input on the cursor position
- ///
- bool SupportsPreedit { get; }
- ///
- /// Sets the non-committed input string
- ///
- void SetPreeditText(string? text);
-
- ///
- /// Sets the current composing region. This doesn't remove the composing text from the commited text.
- ///
- void SetComposingRegion(TextRange? region);
-
- ///
- /// Indicates if text input client is capable of providing the text around the cursor
- ///
- bool SupportsSurroundingText { get; }
- ///
- /// Returns the text around the cursor, usually the current paragraph, the cursor position inside that text and selection start position
- ///
- TextInputMethodSurroundingText SurroundingText { get; }
- ///
- /// Should be fired when surrounding text changed
- ///
- event EventHandler? SurroundingTextChanged;
-
- ///
- /// Gets or sets a platform editable. Text and selection changes made in the editable are forwarded to the IM client.
- ///
- ITextEditable? TextEditable { get; set; }
-
- void SelectInSurroundingText(int start, int end);
- }
-
- public record struct TextInputMethodSurroundingText
- {
- public string Text { get; set; }
- public int CursorOffset { get; set; }
- public int AnchorOffset { get; set; }
- }
-}
diff --git a/src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs b/src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs
index be7ad81f81..2969b2e60d 100644
--- a/src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs
+++ b/src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs
@@ -5,7 +5,7 @@ namespace Avalonia.Input.TextInput
[Unstable]
public interface ITextInputMethodImpl
{
- void SetClient(ITextInputMethodClient? client);
+ void SetClient(TextInputMethodClient? client);
void SetCursorRect(Rect rect);
void SetOptions(TextInputOptions options);
void Reset();
diff --git a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs
index c7fca04ea9..387179b970 100644
--- a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs
+++ b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs
@@ -7,7 +7,7 @@ namespace Avalonia.Input.TextInput
{
private ITextInputMethodImpl? _im;
private IInputElement? _focusedElement;
- private ITextInputMethodClient? _client;
+ private TextInputMethodClient? _client;
private readonly TransformTrackingHelper _transformTracker = new TransformTrackingHelper();
public TextInputMethodManager()
@@ -16,7 +16,7 @@ namespace Avalonia.Input.TextInput
InputMethod.IsInputMethodEnabledProperty.Changed.Subscribe(OnIsInputMethodEnabledChanged);
}
- private ITextInputMethodClient? Client
+ private TextInputMethodClient? Client
{
get => _client;
set
diff --git a/src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs b/src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs
new file mode 100644
index 0000000000..7cf3752057
--- /dev/null
+++ b/src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs
@@ -0,0 +1,84 @@
+using System;
+
+namespace Avalonia.Input.TextInput
+{
+ public abstract class TextInputMethodClient
+ {
+ ///
+ /// Fires when the text view visual has changed
+ ///
+ public event EventHandler? TextViewVisualChanged;
+
+ ///
+ /// Fires when the cursor rectangle has changed
+ ///
+ public event EventHandler? CursorRectangleChanged;
+
+ ///
+ /// Fires when the surrounding text has changed
+ ///
+ public event EventHandler? SurroundingTextChanged;
+
+ ///
+ /// Fires when the selection has changed
+ ///
+ public event EventHandler? SelectionChanged;
+
+ ///
+ /// The visual that's showing the text
+ ///
+ public abstract Visual TextViewVisual { get; }
+
+ ///
+ /// Indicates if TextViewVisual is capable of displaying non-committed input on the cursor position
+ ///
+ public abstract bool SupportsPreedit { get; }
+
+ ///
+ /// Indicates if text input client is capable of providing the text around the cursor
+ ///
+ public abstract bool SupportsSurroundingText { get; }
+
+ ///
+ /// Returns the text around the cursor, usually the current paragraph
+ ///
+ public abstract string SurroundingText { get; }
+
+ ///
+ /// Gets the cursor rectangle relative to the TextViewVisual
+ ///
+ public abstract Rect CursorRectangle { get; }
+
+ ///
+ /// Gets or sets the curent selection range within current surrounding text.
+ ///
+ public abstract TextSelection Selection { get; set; }
+
+ ///
+ /// Sets the non-committed input string
+ ///
+ public virtual void SetPreeditText(string? preeditText) { }
+
+ protected virtual void RaiseTextViewVisualChanged()
+ {
+ TextViewVisualChanged?.Invoke(this, EventArgs.Empty);
+ }
+
+ protected virtual void RaiseCursorRectangleChanged()
+ {
+ CursorRectangleChanged?.Invoke(this, EventArgs.Empty);
+ }
+
+ protected virtual void RaiseSurroundingTextChanged()
+ {
+ SurroundingTextChanged?.Invoke(this, EventArgs.Empty);
+ }
+
+ protected virtual void RaiseSelectionChanged()
+ {
+ SelectionChanged?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ public record struct TextSelection(int Start, int End);
+}
diff --git a/src/Avalonia.Base/Input/TextInput/TextInputMethodClientRequestedEventArgs.cs b/src/Avalonia.Base/Input/TextInput/TextInputMethodClientRequestedEventArgs.cs
index bec43487d2..d90d4257fa 100644
--- a/src/Avalonia.Base/Input/TextInput/TextInputMethodClientRequestedEventArgs.cs
+++ b/src/Avalonia.Base/Input/TextInput/TextInputMethodClientRequestedEventArgs.cs
@@ -7,6 +7,6 @@ namespace Avalonia.Input.TextInput
///
/// Set this property to a valid text input client to enable input method interaction
///
- public ITextInputMethodClient? Client { get; set; }
+ public TextInputMethodClient? Client { get; set; }
}
}
diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs
index c5b4834ded..43c4e76aba 100644
--- a/src/Avalonia.Controls/Presenters/TextPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs
@@ -50,15 +50,6 @@ namespace Avalonia.Controls.Presenters
public static readonly StyledProperty PreeditTextProperty =
AvaloniaProperty.Register(nameof(PreeditText));
- ///
- /// Defines the property.
- ///
- public static readonly DirectProperty CompositionRegionProperty =
- AvaloniaProperty.RegisterDirect(
- nameof(CompositionRegion),
- o => o.CompositionRegion,
- (o, v) => o.CompositionRegion = v);
-
///
/// Defines the property.
///
@@ -97,7 +88,6 @@ namespace Avalonia.Controls.Presenters
private CharacterHit _lastCharacterHit;
private Rect _caretBounds;
private Point _navigationPosition;
- private TextRange? _compositionRegion;
static TextPresenter()
{
@@ -137,12 +127,6 @@ namespace Avalonia.Controls.Presenters
set => SetValue(PreeditTextProperty, value);
}
- public TextRange? CompositionRegion
- {
- get => _compositionRegion;
- set => SetAndRaise(CompositionRegionProperty, ref _compositionRegion, value);
- }
-
///
/// Gets or sets the font family.
///
@@ -490,10 +474,10 @@ namespace Avalonia.Controls.Presenters
{
TextLayout result;
- var text = Text;
-
+ var caretIndex = CaretIndex;
+ var preeditText = PreeditText;
+ var text = GetCombinedText(Text, caretIndex, preeditText);
var typeface = new Typeface(FontFamily, FontStyle, FontWeight);
-
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
var start = Math.Min(selectionStart, selectionEnd);
@@ -503,22 +487,9 @@ namespace Avalonia.Controls.Presenters
var foreground = Foreground;
- if (_compositionRegion != null)
- {
- var preeditHighlight = new ValueSpan(_compositionRegion?.Start ?? 0, _compositionRegion?.Length ?? 0,
- new GenericTextRunProperties(typeface, FontSize,
- foregroundBrush: foreground,
- textDecorations: TextDecorations.Underline));
-
- textStyleOverrides = new[]
- {
- preeditHighlight
- };
-
- }
- else if (!string.IsNullOrEmpty(PreeditText))
+ if (!string.IsNullOrEmpty(preeditText))
{
- var preeditHighlight = new ValueSpan(CaretIndex, PreeditText.Length,
+ var preeditHighlight = new ValueSpan(caretIndex, preeditText.Length,
new GenericTextRunProperties(typeface, FontSize,
foregroundBrush: foreground,
textDecorations: TextDecorations.Underline));
@@ -554,6 +525,27 @@ namespace Avalonia.Controls.Presenters
return result;
}
+ private static string? GetCombinedText(string? text, int caretIndex, string? preeditText)
+ {
+ if (string.IsNullOrEmpty(preeditText))
+ {
+ return text;
+ }
+
+ if (string.IsNullOrEmpty(text))
+ {
+ return preeditText;
+ }
+
+ var sb = StringBuilderCache.Acquire(text.Length + preeditText.Length);
+
+ sb.Append(text.Substring(0, caretIndex));
+ sb.Insert(caretIndex, preeditText);
+ sb.Append(text.Substring(caretIndex));
+
+ return StringBuilderCache.GetStringAndRelease(sb);
+ }
+
protected virtual void InvalidateTextLayout()
{
_textLayout?.Dispose();
@@ -831,6 +823,18 @@ namespace Avalonia.Controls.Presenters
_caretTimer.Tick -= CaretTimerTick;
}
+ private void OnPreeditTextChanged(string? preeditText)
+ {
+ if (string.IsNullOrEmpty(preeditText))
+ {
+ UpdateCaret(new CharacterHit(CaretIndex), false);
+ }
+ else
+ {
+ UpdateCaret(new CharacterHit(CaretIndex + preeditText.Length), false);
+ }
+ }
+
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
@@ -840,10 +844,30 @@ namespace Avalonia.Controls.Presenters
MoveCaretToTextPosition(change.GetNewValue());
}
+ if(change.Property == PreeditTextProperty)
+ {
+ OnPreeditTextChanged(change.NewValue as string);
+ }
+
+ if(change.Property == TextProperty)
+ {
+ if (!string.IsNullOrEmpty(PreeditText))
+ {
+ SetCurrentValue(PreeditTextProperty, null);
+ }
+ }
+
+ if(change.Property == CaretIndexProperty)
+ {
+ if (!string.IsNullOrEmpty(PreeditText))
+ {
+ SetCurrentValue(PreeditTextProperty, null);
+ }
+ }
+
switch (change.Property.Name)
{
case nameof(PreeditText):
- case nameof(CompositionRegion):
case nameof(Foreground):
case nameof(FontSize):
case nameof(FontStyle):
diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs
index 37e7b7beac..98b3b13c17 100644
--- a/src/Avalonia.Controls/TextBox.cs
+++ b/src/Avalonia.Controls/TextBox.cs
@@ -1000,7 +1000,9 @@ namespace Avalonia.Controls
textBuilder.Insert(caretIndex, input);
- SetCurrentValue(TextProperty, StringBuilderCache.GetStringAndRelease(textBuilder));
+ var text = StringBuilderCache.GetStringAndRelease(textBuilder);
+
+ SetCurrentValue(TextProperty, text);
ClearSelection();
diff --git a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
index e999d0bb74..28a230640a 100644
--- a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
+++ b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
@@ -1,243 +1,114 @@
using System;
-using System.Diagnostics;
using Avalonia.Controls.Presenters;
using Avalonia.Input.TextInput;
-using Avalonia.Media;
using Avalonia.Media.TextFormatting;
-using Avalonia.Threading;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
- internal class TextBoxTextInputMethodClient : ITextInputMethodClient
+ internal class TextBoxTextInputMethodClient : TextInputMethodClient
{
private TextBox? _parent;
private TextPresenter? _presenter;
- private ITextEditable? _textEditable;
- public Visual TextViewVisual => _presenter!;
+ public override Visual TextViewVisual => _presenter!;
- public bool SupportsPreedit => true;
-
- public bool SupportsSurroundingText => true;
-
- public Rect CursorRectangle
+ public override string SurroundingText
{
get
{
- if (_parent == null || _presenter == null)
+ if (_presenter is null || _parent is null)
{
- return default;
+ return "";
}
-
- var transform = _presenter.TransformToVisual(_parent);
-
- if (transform == null)
+
+ if (_parent.CaretIndex != _presenter.CaretIndex)
{
- return default;
+ _presenter.SetCurrentValue(TextPresenter.CaretIndexProperty, _parent.CaretIndex);
}
- var rect = _presenter.GetCursorRectangle().TransformToAABB(transform.Value);
-
- return rect;
- }
- }
-
- public TextInputMethodSurroundingText SurroundingText
- {
- get
- {
- if (_presenter is null || _parent is null)
+ if (_parent.Text != _presenter.Text)
{
- return default;
+ _presenter.SetCurrentValue(TextPresenter.TextProperty, _parent.Text);
}
-
+
var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(_presenter.CaretIndex, false);
var textLine = _presenter.TextLayout.TextLines[lineIndex];
- var lineStart = textLine.FirstTextSourceIndex;
-
var lineText = GetTextLineText(textLine);
- var anchorOffset = Math.Max(0, _parent.SelectionStart - lineStart);
-
- var cursorOffset = Math.Max(0, _presenter.SelectionEnd - lineStart);
-
- return new TextInputMethodSurroundingText
- {
- Text = lineText ?? "",
- AnchorOffset = anchorOffset,
- CursorOffset = cursorOffset
- };
+ return lineText;
}
}
- public ITextEditable? TextEditable
+ public override Rect CursorRectangle
{
- get => _textEditable; set
+ get
{
- if (_textEditable != null)
+ if (_parent == null || _presenter == null)
{
- _textEditable.TextChanged -= TextEditable_TextChanged;
- _textEditable.SelectionChanged -= TextEditable_SelectionChanged;
- _textEditable.CompositionChanged -= TextEditable_CompositionChanged;
+ return default;
}
- _textEditable = value;
+ var transform = _presenter.TransformToVisual(_parent);
- if (_textEditable != null)
+ if (transform == null)
{
- _textEditable.TextChanged += TextEditable_TextChanged;
- _textEditable.SelectionChanged += TextEditable_SelectionChanged;
- _textEditable.CompositionChanged += TextEditable_CompositionChanged;
-
- if (_presenter != null)
- {
- _textEditable.Text = _presenter.Text;
- _textEditable.SelectionStart = _presenter.SelectionStart;
- _textEditable.SelectionEnd = _presenter.SelectionEnd;
- }
+ return default;
}
- }
- }
- private void TextEditable_CompositionChanged(object? sender, EventArgs e)
- {
- if (_presenter != null && _textEditable != null)
- {
- _presenter.SetCurrentValue(TextPresenter.CompositionRegionProperty, new TextRange(_textEditable.CompositionStart, _textEditable.CompositionEnd));
+ return _presenter.GetCursorRectangle().TransformToAABB(transform.Value);
}
}
- private void TextEditable_SelectionChanged(object? sender, EventArgs e)
+ public override TextSelection Selection
{
- if (_parent != null && _textEditable != null)
- {
- _parent.SelectionStart = _textEditable.SelectionStart;
- _parent.SelectionEnd = _textEditable.SelectionEnd;
- }
- }
-
- private void TextEditable_TextChanged(object? sender, EventArgs e)
- {
- if (_parent != null)
- {
- if (_parent.Text != _textEditable?.Text)
- {
- _parent.Text = _textEditable?.Text;
- }
- }
- }
-
- private static string GetTextLineText(TextLine textLine)
- {
- var builder = StringBuilderCache.Acquire(textLine.Length);
-
- foreach (var run in textLine.TextRuns)
+ get
{
- if (run.Length > 0)
+ if (_presenter is null || _parent is null)
{
-#if NET6_0_OR_GREATER
- builder.Append(run.Text.Span);
-#else
- builder.Append(run.Text.Span.ToArray());
-#endif
+ return default;
}
- }
-
- var lineText = builder.ToString();
- StringBuilderCache.Release(builder);
-
- return lineText;
- }
-
- public event EventHandler? TextViewVisualChanged;
+ var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(_parent.CaretIndex, false);
- public event EventHandler? CursorRectangleChanged;
+ var textLine = _presenter.TextLayout.TextLines[lineIndex];
- public event EventHandler? SurroundingTextChanged;
+ var lineStart = textLine.FirstTextSourceIndex;
- private string? _presenterText;
- private int _compositionStart;
+ var selectionStart = Math.Max(0, _parent.SelectionStart - lineStart);
- public void SetPreeditText(string? preeditText)
- {
- if (_presenter == null || _parent == null)
- {
- return;
- }
+ var selectionEnd = Math.Max(0, _parent.SelectionEnd - lineStart);
- if (_presenterText is null)
- {
- _presenterText = _parent.Text ?? "";
- _compositionStart = _parent.CaretIndex;
+ return new TextSelection(selectionStart, selectionEnd);
}
-
- var text = GetText(preeditText);
-
- _presenter.SetCurrentValue(TextPresenter.TextProperty, text);
-
- _presenter.SetCurrentValue(TextPresenter.PreeditTextProperty, preeditText);
-
- _presenter.UpdateCaret(new CharacterHit(_compositionStart + (preeditText != null ? preeditText.Length : 0)), false);
-
- if (string.IsNullOrEmpty(preeditText))
+ set
{
- _presenterText = null;
- }
- }
+ if (_parent is null || _presenter is null)
+ {
+ return;
+ }
- private string? GetText(string? preeditText)
- {
- if (string.IsNullOrEmpty(preeditText))
- {
- return _presenterText;
- }
+ var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(_parent.CaretIndex, false);
- if (string.IsNullOrEmpty(_presenterText))
- {
- return preeditText;
- }
+ var textLine = _presenter.TextLayout.TextLines[lineIndex];
- var sb = StringBuilderCache.Acquire(_presenterText.Length + preeditText.Length);
+ var lineStart = textLine.FirstTextSourceIndex;
- sb.Append(_presenterText);
- sb.Insert(_compositionStart, preeditText);
+ var selectionStart = lineStart + value.Start;
+ var selectionEnd = lineStart + value.End;
- return StringBuilderCache.GetStringAndRelease(sb);
- }
+ _parent.SelectionStart = selectionStart;
+ _parent.SelectionEnd = selectionEnd;
- public void SetComposingRegion(TextRange? region)
- {
- if (_presenter == null)
- {
- return;
+ RaiseSelectionChanged();
}
-
- _presenter.SetCurrentValue(TextPresenter.CompositionRegionProperty, region);
}
- public void SelectInSurroundingText(int start, int end)
- {
- if (_parent is null || _presenter is null)
- {
- return;
- }
-
- var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(_presenter.CaretIndex, false);
-
- var textLine = _presenter.TextLayout.TextLines[lineIndex];
-
- var lineStart = textLine.FirstTextSourceIndex;
+ public override bool SupportsPreedit => true;
- var selectionStart = lineStart + start;
- var selectionEnd = lineStart + end;
-
- _parent.SelectionStart = selectionStart;
- _parent.SelectionEnd = selectionEnd;
- }
+ public override bool SupportsSurroundingText => true;
public void SetPresenter(TextPresenter? presenter, TextBox? parent)
{
@@ -253,66 +124,71 @@ namespace Avalonia.Controls
_parent.PropertyChanged += OnParentPropertyChanged;
}
- if (_presenter != null)
- {
- _presenter.ClearValue(TextPresenter.PreeditTextProperty);
+ var oldPresenter = _presenter;
- _presenter.ClearValue(TextPresenter.CompositionRegionProperty);
+ if (oldPresenter != null)
+ {
+ oldPresenter.ClearValue(TextPresenter.PreeditTextProperty);
- _presenter.CaretBoundsChanged -= OnCaretBoundsChanged;
+ oldPresenter.CaretBoundsChanged -= (s,e) => RaiseCursorRectangleChanged();
}
_presenter = presenter;
if (_presenter != null)
{
- _presenter.CaretBoundsChanged += OnCaretBoundsChanged;
+ _presenter.CaretBoundsChanged += (s, e) => RaiseCursorRectangleChanged();
}
- TextViewVisualChanged?.Invoke(this, EventArgs.Empty);
+ RaiseTextViewVisualChanged();
- OnCaretBoundsChanged(this, EventArgs.Empty);
+ RaiseCursorRectangleChanged();
}
- private void OnParentPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
+ public override void SetPreeditText(string? preeditText)
{
- if (e.Property == TextBox.SelectionStartProperty || e.Property == TextBox.SelectionEndProperty)
+ if (_presenter == null || _parent == null)
{
- if (SupportsSurroundingText)
- {
- SurroundingTextChanged?.Invoke(this, e);
- }
- if (_textEditable != null)
- {
- var value = (int)(e.NewValue ?? 0);
- if (e.Property == TextBox.SelectionStartProperty)
- {
- _textEditable.SelectionStart = value;
- }
-
- if (e.Property == TextBox.SelectionEndProperty)
- {
- _textEditable.SelectionEnd = value;
- }
- }
+ return;
}
- if (e.Property == TextBox.TextProperty)
+ _presenter.SetCurrentValue(TextPresenter.PreeditTextProperty, preeditText);
+ }
+
+ private static string GetTextLineText(TextLine textLine)
+ {
+ var builder = StringBuilderCache.Acquire(textLine.Length);
+
+ foreach (var run in textLine.TextRuns)
{
- if (_textEditable != null)
+ if (run.Length > 0)
{
- _textEditable.Text = (string?)e.NewValue;
+#if NET6_0_OR_GREATER
+ builder.Append(run.Text.Span);
+#else
+ builder.Append(run.Text.Span.ToArray());
+#endif
}
}
+
+ var lineText = builder.ToString();
+
+ StringBuilderCache.Release(builder);
+
+ return lineText;
}
- private void OnCaretBoundsChanged(object? sender, EventArgs e)
+ private void OnParentPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
- Dispatcher.UIThread.Post(() =>
+ if (e.Property == TextBox.TextProperty)
{
- CursorRectangleChanged?.Invoke(this, e);
+ RaiseSurroundingTextChanged();
+ }
- }, DispatcherPriority.Input);
+ if (e.Property == TextBox.SelectionStartProperty || e.Property == TextBox.SelectionEndProperty)
+ {
+ RaiseSelectionChanged();
+ }
}
}
}
diff --git a/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs b/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs
index 936e856cf0..9ce6604594 100644
--- a/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs
+++ b/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs
@@ -41,7 +41,7 @@ namespace Avalonia.FreeDesktop.DBusIme
private PixelRect? _lastReportedRect;
private double _scaling = 1;
private PixelPoint _windowPosition;
- private ITextInputMethodClient? _client;
+ private TextInputMethodClient? _client;
protected bool IsConnected => _currentName != null;
@@ -53,7 +53,7 @@ namespace Avalonia.FreeDesktop.DBusIme
_ = WatchAsync();
}
- public ITextInputMethodClient Client => _client;
+ public TextInputMethodClient Client => _client;
public bool IsActive => _client is not null;
@@ -210,7 +210,7 @@ namespace Avalonia.FreeDesktop.DBusIme
UpdateActive();
}
- void ITextInputMethodImpl.SetClient(ITextInputMethodClient? client)
+ void ITextInputMethodImpl.SetClient(TextInputMethodClient? client)
{
_client = client;
UpdateActive();
diff --git a/src/Avalonia.Native/AvaloniaNativeTextInputMethod.cs b/src/Avalonia.Native/AvaloniaNativeTextInputMethod.cs
index 4c93c06ef3..0171ada7b9 100644
--- a/src/Avalonia.Native/AvaloniaNativeTextInputMethod.cs
+++ b/src/Avalonia.Native/AvaloniaNativeTextInputMethod.cs
@@ -1,5 +1,6 @@
using System;
using Avalonia.Input.TextInput;
+using Avalonia.Media.TextFormatting;
using Avalonia.Native.Interop;
#nullable enable
@@ -8,7 +9,7 @@ namespace Avalonia.Native
{
internal class AvaloniaNativeTextInputMethod : ITextInputMethodImpl, IDisposable
{
- private ITextInputMethodClient? _client;
+ private TextInputMethodClient? _client;
private IAvnTextInputMethodClient? _nativeClient;
private readonly IAvnTextInputMethod _inputMethod;
@@ -28,7 +29,7 @@ namespace Avalonia.Native
_inputMethod.Reset();
}
- public void SetClient(ITextInputMethodClient? client)
+ public void SetClient(TextInputMethodClient? client)
{
if (_client is { SupportsSurroundingText: true })
{
@@ -96,11 +97,12 @@ namespace Avalonia.Native
}
var surroundingText = _client.SurroundingText;
+ var selection = _client.Selection;
_inputMethod.SetSurroundingText(
- surroundingText.Text ?? "",
- surroundingText.AnchorOffset,
- surroundingText.CursorOffset
+ surroundingText ?? "",
+ selection.Start,
+ selection.End
);
}
@@ -116,9 +118,9 @@ namespace Avalonia.Native
private class AvnTextInputMethodClient : NativeCallbackBase, IAvnTextInputMethodClient
{
- private readonly ITextInputMethodClient _client;
+ private readonly TextInputMethodClient _client;
- public AvnTextInputMethodClient(ITextInputMethodClient client)
+ public AvnTextInputMethodClient(TextInputMethodClient client)
{
_client = client;
}
@@ -135,7 +137,7 @@ namespace Avalonia.Native
{
if (_client.SupportsSurroundingText)
{
- _client.SelectInSurroundingText(start, end);
+ _client.Selection = new TextSelection(start, end);
}
}
}
diff --git a/src/Avalonia.X11/X11Window.Xim.cs b/src/Avalonia.X11/X11Window.Xim.cs
index cc5ecad09f..3dc860d612 100644
--- a/src/Avalonia.X11/X11Window.Xim.cs
+++ b/src/Avalonia.X11/X11Window.Xim.cs
@@ -15,14 +15,14 @@ namespace Avalonia.X11
private readonly X11Window _parent;
private bool _windowActive, _imeActive;
private Rect? _queuedCursorRect;
- private ITextInputMethodClient? _client;
+ private TextInputMethodClient? _client;
public XimInputMethod(X11Window parent)
{
_parent = parent;
}
- public ITextInputMethodClient? Client => _client;
+ public TextInputMethodClient? Client => _client;
public bool IsActive => _client != null;
@@ -62,7 +62,7 @@ namespace Avalonia.X11
UpdateActive();
}
- public void SetClient(ITextInputMethodClient client)
+ public void SetClient(TextInputMethodClient client)
{
_client = client;
UpdateActive();
diff --git a/src/Browser/Avalonia.Browser/AvaloniaView.cs b/src/Browser/Avalonia.Browser/AvaloniaView.cs
index b5dd08d854..0b88d9c3e9 100644
--- a/src/Browser/Avalonia.Browser/AvaloniaView.cs
+++ b/src/Browser/Avalonia.Browser/AvaloniaView.cs
@@ -42,7 +42,7 @@ namespace Avalonia.Browser
private const SKColorType ColorType = SKColorType.Rgba8888;
private bool _useGL;
- private ITextInputMethodClient? _client;
+ private TextInputMethodClient? _client;
/// ID of the html element where avalonia content should be rendered.
public AvaloniaView(string divId)
@@ -397,7 +397,7 @@ namespace Avalonia.Browser
if(start != -1 && end != -1 && _client != null)
{
- _client.SelectInSurroundingText(start, end);
+ _client.Selection = new TextSelection(start, end);
}
return false;
}
@@ -513,7 +513,7 @@ namespace Avalonia.Browser
InputHelper.FocusElement(_containerElement);
}
- void ITextInputMethodImpl.SetClient(ITextInputMethodClient? client)
+ void ITextInputMethodImpl.SetClient(TextInputMethodClient? client)
{
if (_client != null)
{
@@ -534,9 +534,10 @@ namespace Avalonia.Browser
InputHelper.ShowElement(_inputElement);
InputHelper.FocusElement(_inputElement);
- var surroundingText = _client.SurroundingText;
+ var surroundingText = _client.SurroundingText ?? "";
+ var selection = _client.Selection;
- InputHelper.SetSurroundingText(_inputElement, surroundingText.Text, surroundingText.AnchorOffset, surroundingText.CursorOffset);
+ InputHelper.SetSurroundingText(_inputElement, surroundingText, selection.Start, selection.End);
}
else
{
@@ -548,16 +549,17 @@ namespace Avalonia.Browser
{
if (_client != null)
{
- var surroundingText = _client.SurroundingText;
+ var surroundingText = _client.SurroundingText ?? "";
+ var selection = _client.Selection;
- InputHelper.SetSurroundingText(_inputElement, surroundingText.Text, surroundingText.AnchorOffset, surroundingText.CursorOffset);
+ InputHelper.SetSurroundingText(_inputElement, surroundingText, selection.Start, selection.End);
}
}
void ITextInputMethodImpl.SetCursorRect(Rect rect)
{
InputHelper.FocusElement(_inputElement);
- InputHelper.SetBounds(_inputElement, (int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height, _client?.SurroundingText.CursorOffset ?? 0);
+ InputHelper.SetBounds(_inputElement, (int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height, _client?.Selection.End ?? 0);
InputHelper.FocusElement(_inputElement);
}
diff --git a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs
index 8502f5b41a..495e808a04 100644
--- a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs
+++ b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs
@@ -26,7 +26,7 @@ namespace Avalonia.Win32.Input
private bool _ignoreComposition;
- public ITextInputMethodClient? Client { get; private set; }
+ public TextInputMethodClient? Client { get; private set; }
[MemberNotNullWhen(true, nameof(Client))]
public bool IsActive => Client != null;
@@ -145,7 +145,7 @@ namespace Avalonia.Win32.Input
});
}
- public void SetClient(ITextInputMethodClient? client)
+ public void SetClient(TextInputMethodClient? client)
{
if(Client != null)
{
@@ -325,7 +325,7 @@ namespace Avalonia.Win32.Input
{
Client.SetPreeditText(null);
- if (Client.SupportsSurroundingText && Client.SurroundingText.AnchorOffset != Client.SurroundingText.CursorOffset)
+ if (Client.SupportsSurroundingText && Client.Selection.Start != Client.Selection.End)
{
KeyPress(Key.Delete);
}
diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.Text.cs b/src/iOS/Avalonia.iOS/AvaloniaView.Text.cs
index a1836f3ce4..0b0e169e8a 100644
--- a/src/iOS/Avalonia.iOS/AvaloniaView.Text.cs
+++ b/src/iOS/Avalonia.iOS/AvaloniaView.Text.cs
@@ -29,7 +29,7 @@ public partial class AvaloniaView
private bool IsDrivingText => CurrentAvaloniaResponder is TextInputResponder t && ReferenceEquals(t.NextResponder, this);
- void ITextInputMethodImpl.SetClient(ITextInputMethodClient? client)
+ void ITextInputMethodImpl.SetClient(TextInputMethodClient? client)
{
_client = client;
if (_client == null && IsDrivingText)
diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs
index 74cabb294d..939df1a3a0 100644
--- a/src/iOS/Avalonia.iOS/AvaloniaView.cs
+++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs
@@ -27,7 +27,7 @@ namespace Avalonia.iOS
private TopLevelImpl _topLevelImpl;
private EmbeddableControlRoot _topLevel;
private TouchHandler _touches;
- private ITextInputMethodClient _client;
+ private TextInputMethodClient _client;
private IAvaloniaViewController _controller;
public AvaloniaView()
diff --git a/src/iOS/Avalonia.iOS/TextInputResponder.cs b/src/iOS/Avalonia.iOS/TextInputResponder.cs
index e444a90506..b54969e5ff 100644
--- a/src/iOS/Avalonia.iOS/TextInputResponder.cs
+++ b/src/iOS/Avalonia.iOS/TextInputResponder.cs
@@ -78,7 +78,7 @@ partial class AvaloniaView
public NSObject Copy(NSZone? zone) => this;
}
- public TextInputResponder(AvaloniaView view, ITextInputMethodClient client)
+ public TextInputResponder(AvaloniaView view, TextInputMethodClient client)
{
_view = view;
NextResponder = view;
@@ -88,12 +88,12 @@ partial class AvaloniaView
public override UIResponder NextResponder { get; }
- private readonly ITextInputMethodClient _client;
+ private readonly TextInputMethodClient _client;
private int _inSurroundingTextUpdateEvent;
private readonly UITextPosition _beginningOfDocument = new AvaloniaTextPosition(0);
private readonly UITextInputStringTokenizer _tokenizer;
- public ITextInputMethodClient? Client => _client;
+ public TextInputMethodClient? Client => _client;
public override bool CanResignFirstResponder => true;
@@ -201,17 +201,23 @@ partial class AvaloniaView
string IUITextInput.TextInRange(UITextRange range)
{
var r = (AvaloniaTextRange)range;
- var s = _client.SurroundingText;
+ var surroundingText = _client.SurroundingText;
+
+ var currentSelection = _client.Selection;
+
Logger.TryGet(LogEventLevel.Debug, ImeLog)?.Log(null, "IUIKeyInput.TextInRange {start} {end}", r.StartIndex, r.EndIndex);
string result = "";
if (string.IsNullOrEmpty(_markedText))
- result = s.Text[r.StartIndex..r.EndIndex];
+ if(surroundingText != null && r.EndIndex < surroundingText.Length)
+ {
+ result = surroundingText[r.StartIndex..r.EndIndex];
+ }
else
{
- var span = new CombinedSpan3(s.Text.AsSpan().Slice(0, s.CursorOffset),
+ var span = new CombinedSpan3(surroundingText.AsSpan().Slice(0, currentSelection.Start),
_markedText,
- s.Text.AsSpan().Slice(s.CursorOffset));
+ surroundingText.AsSpan().Slice(currentSelection.Start));
var buf = new char[r.EndIndex - r.StartIndex];
span.CopyTo(buf, r.StartIndex);
result = new string(buf);
@@ -226,7 +232,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.SelectInSurroundingText(r.StartIndex, r.EndIndex);
+ _client.Selection = new TextSelection(r.StartIndex, r.EndIndex);
TextInput(text);
}
@@ -447,21 +453,19 @@ partial class AvaloniaView
{
get
{
- return new AvaloniaTextRange(
- Math.Min(_client.SurroundingText.CursorOffset, _client.SurroundingText.AnchorOffset),
- Math.Max(_client.SurroundingText.CursorOffset, _client.SurroundingText.AnchorOffset));
+ return new AvaloniaTextRange(_client.Selection.Start, _client.Selection.End);
}
set
{
if (_inSurroundingTextUpdateEvent > 0)
return;
if (value == null)
- _client.SelectInSurroundingText(_client.SurroundingText.CursorOffset,
- _client.SurroundingText.CursorOffset);
+ _client.Selection = default;
else
{
var r = (AvaloniaTextRange)value;
- _client.SelectInSurroundingText(r.StartIndex, r.EndIndex);
+
+ _client.Selection = new TextSelection(r.StartIndex, r.EndIndex);
}
}
}
@@ -474,7 +478,7 @@ partial class AvaloniaView
UITextPosition IUITextInput.BeginningOfDocument => _beginningOfDocument;
- private int DocumentLength => (_client.SurroundingText.Text?.Length ?? 0) + (_markedText?.Length ?? 0);
+ private int DocumentLength => (_client.SurroundingText?.Length ?? 0) + (_markedText?.Length ?? 0);
UITextPosition IUITextInput.EndOfDocument => new AvaloniaTextPosition(DocumentLength);
UITextRange IUITextInput.MarkedTextRange
@@ -483,7 +487,7 @@ partial class AvaloniaView
{
if (string.IsNullOrWhiteSpace(_markedText))
return null!;
- return new AvaloniaTextRange(_client.SurroundingText.CursorOffset, _client.SurroundingText.CursorOffset + _markedText.Length);
+ return new AvaloniaTextRange(_client.Selection.Start, _client.Selection.Start + _markedText.Length);
}
}