Browse Source

Merge pull request #12270 from AvaloniaUI/android-text

Android - Text Input fixes
pull/12313/head
Max Katz 3 years ago
committed by GitHub
parent
commit
14f89140ca
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 69
      src/Android/Avalonia.Android/AndroidInputMethod.cs
  2. 108
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

69
src/Android/Avalonia.Android/AndroidInputMethod.cs

@ -18,6 +18,8 @@ namespace Avalonia.Android
public bool IsActive { get; } public bool IsActive { get; }
public InputMethodManager IMM { get; } public InputMethodManager IMM { get; }
void OnBatchEditedEnded();
} }
enum CustomImeFlags enum CustomImeFlags
@ -103,6 +105,13 @@ namespace Avalonia.Android
} }
private void _client_SelectionChanged(object sender, EventArgs e) private void _client_SelectionChanged(object sender, EventArgs e)
{
if (_inputConnection.IsInBatchEdit)
return;
OnSelectionChanged();
}
private void OnSelectionChanged()
{ {
var selection = Client.Selection; var selection = Client.Selection;
@ -113,17 +122,67 @@ namespace Avalonia.Android
private void _client_SurroundingTextChanged(object sender, EventArgs e) private void _client_SurroundingTextChanged(object sender, EventArgs e)
{ {
if (_inputConnection.IsInBatchEdit)
return;
OnSurroundingTextChanged();
}
public void OnBatchEditedEnded()
{
if (_inputConnection.IsInBatchEdit)
return;
OnSurroundingTextChanged();
OnSelectionChanged();
}
private void OnSurroundingTextChanged()
{
if(_client is null)
{
return;
}
var surroundingText = _client.SurroundingText ?? ""; var surroundingText = _client.SurroundingText ?? "";
var editableText = _inputConnection.EditableWrapper.ToString();
_inputConnection.EditableWrapper.IgnoreChange = true; if (editableText != surroundingText)
{
_inputConnection.EditableWrapper.IgnoreChange = true;
_inputConnection.Editable.Replace(0, _inputConnection.Editable.Length(), surroundingText); var diff = GetDiff();
_inputConnection.EditableWrapper.IgnoreChange = false; _inputConnection.Editable.Replace(diff.index, editableText.Length, diff.diff);
var selection = Client.Selection; _inputConnection.EditableWrapper.IgnoreChange = false;
_imm.UpdateSelection(_host, selection.Start, selection.End, selection.Start, selection.End); if(diff.index == 0)
{
var selection = _client.Selection;
_client.Selection = new TextSelection(selection.Start, 0);
_client.Selection = selection;
}
}
(int index, string diff) GetDiff()
{
int index = 0;
var longerLength = Math.Max(surroundingText.Length, editableText.Length);
for (int i = 0; i < longerLength; i++)
{
if (surroundingText.Length == i || editableText.Length == i || surroundingText[i] != editableText[i])
{
index = i;
break;
}
}
var diffString = surroundingText.Substring(index, surroundingText.Length - index);
return (index, diffString);
}
} }
public void SetCursorRect(Rect rect) public void SetCursorRect(Rect rect)

108
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using System.Threading;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.Graphics; using Android.Graphics;
@ -447,19 +448,22 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{ {
private readonly AvaloniaInputConnection _inputConnection; private readonly AvaloniaInputConnection _inputConnection;
public event EventHandler<int> SelectionChanged;
public EditableWrapper(AvaloniaInputConnection inputConnection) public EditableWrapper(AvaloniaInputConnection inputConnection)
{ {
_inputConnection = inputConnection; _inputConnection = inputConnection;
} }
public TextSelection CurrentSelection => new TextSelection(Selection.GetSelectionStart(this), Selection.GetSelectionEnd(this));
public TextSelection CurrentComposition => new TextSelection(BaseInputConnection.GetComposingSpanStart(this), BaseInputConnection.GetComposingSpanEnd(this));
public bool IgnoreChange { get; set; } public bool IgnoreChange { get; set; }
public override IEditable Replace(int start, int end, ICharSequence tb) public override IEditable Replace(int start, int end, ICharSequence tb)
{ {
if (!IgnoreChange && start != end) if (!IgnoreChange && start != end)
{ {
var text = tb.SubSequence(0, tb.Length());
SelectSurroundingTextForDeletion(start, end); SelectSurroundingTextForDeletion(start, end);
} }
@ -470,8 +474,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{ {
if (!IgnoreChange && start != end) if (!IgnoreChange && start != end)
{ {
var text = tb.SubSequence(tbstart, tbend);
SelectSurroundingTextForDeletion(start, end); SelectSurroundingTextForDeletion(start, end);
} }
@ -490,8 +492,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
private readonly IAndroidInputMethod _inputMethod; private readonly IAndroidInputMethod _inputMethod;
private readonly EditableWrapper _editable; private readonly EditableWrapper _editable;
private bool _commitInProgress; private bool _commitInProgress;
private (int Start, int End)? _composingRegion; private int _batchLevel = 0;
private TextSelection _selection;
public AvaloniaInputConnection(TopLevelImpl toplevel, IAndroidInputMethod inputMethod) : base(inputMethod.View, true) public AvaloniaInputConnection(TopLevelImpl toplevel, IAndroidInputMethod inputMethod) : base(inputMethod.View, true)
{ {
@ -510,63 +511,87 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public TopLevelImpl Toplevel => _toplevel; public TopLevelImpl Toplevel => _toplevel;
public bool IsInBatchEdit => _batchLevel > 0;
public override bool SetComposingRegion(int start, int end) public override bool SetComposingRegion(int start, int end)
{ {
_composingRegion = new(start, end);
return base.SetComposingRegion(start, end); return base.SetComposingRegion(start, end);
} }
public override bool SetComposingText(ICharSequence text, int newCursorPosition) public override bool SetComposingText(ICharSequence text, int newCursorPosition)
{ {
if(_composingRegion != null) BeginBatchEdit();
_editable.IgnoreChange = true;
try
{ {
// Select the composing region. if (_editable.CurrentComposition.Start > -1)
InputMethod.Client.Selection = new TextSelection(_composingRegion.Value.Start, _composingRegion.Value.End); {
} // Select the composing region.
var compositionText = text.SubSequence(0, text.Length()); InputMethod.Client.Selection = new TextSelection(_editable.CurrentComposition.Start, _editable.CurrentComposition.End);
}
var compositionText = text.SubSequence(0, text.Length());
if (_inputMethod.IsActive && !_commitInProgress) if (_inputMethod.IsActive && !_commitInProgress)
{
if (string.IsNullOrEmpty(compositionText))
_inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
else
_toplevel.TextInput(compositionText);
}
base.SetComposingText(text, newCursorPosition);
}
finally
{ {
if (string.IsNullOrEmpty(compositionText)) _editable.IgnoreChange = false;
_inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
else EndBatchEdit();
_toplevel.TextInput(compositionText);
} }
return true; return true;
} }
public override bool BeginBatchEdit()
{
_batchLevel = Interlocked.Increment(ref _batchLevel);
return base.BeginBatchEdit();
}
public override bool EndBatchEdit()
{
_batchLevel = Interlocked.Decrement(ref _batchLevel);
_inputMethod.OnBatchEditedEnded();
return base.EndBatchEdit();
}
public override bool CommitText(ICharSequence text, int newCursorPosition) public override bool CommitText(ICharSequence text, int newCursorPosition)
{ {
BeginBatchEdit();
_commitInProgress = true; _commitInProgress = true;
var ret = base.CommitText(text, newCursorPosition); var composingRegion = _editable.CurrentComposition;
var committedText = text.SubSequence(0, text.Length()); var ret = base.CommitText(text, newCursorPosition);
if (_inputMethod.IsActive && !string.IsNullOrEmpty(committedText)) if(composingRegion.Start != -1)
{ {
if(_composingRegion != null) InputMethod.Client.Selection = composingRegion;
{ }
_inputMethod.Client.Selection = new TextSelection(_composingRegion.Value.Start, _composingRegion.Value.End);
}
_toplevel.TextInput(committedText); var committedText = text.SubSequence(0, text.Length());
_composingRegion = null; if (_inputMethod.IsActive)
} if (string.IsNullOrEmpty(committedText))
_inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
else
_toplevel.TextInput(committedText);
_commitInProgress = false; _commitInProgress = false;
EndBatchEdit();
return ret; return true;
}
public override bool FinishComposingText()
{
_composingRegion = null;
return base.FinishComposingText();
} }
public override bool DeleteSurroundingText(int beforeLength, int afterLength) public override bool DeleteSurroundingText(int beforeLength, int afterLength)
@ -575,11 +600,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{ {
EditableWrapper.IgnoreChange = true; EditableWrapper.IgnoreChange = true;
} }
var result = base.DeleteSurroundingText(beforeLength, afterLength);
if (InputMethod.IsActive) if (InputMethod.IsActive)
{ {
var selection = _selection; var selection = _editable.CurrentSelection;
InputMethod.Client.Selection = new TextSelection(selection.Start - beforeLength, selection.End + afterLength); InputMethod.Client.Selection = new TextSelection(selection.Start - beforeLength, selection.End + afterLength);
@ -588,13 +612,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
EditableWrapper.IgnoreChange = true; EditableWrapper.IgnoreChange = true;
} }
return result; return true;
}
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) public override bool PerformEditorAction([GeneratedEnum] ImeAction actionCode)
@ -630,7 +648,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
return null; return null;
} }
var selection = _selection; var selection = _editable.CurrentSelection;
ExtractedText extract = new ExtractedText ExtractedText extract = new ExtractedText
{ {

Loading…
Cancel
Save