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 InputMethodManager IMM { get; }
void OnBatchEditedEnded();
}
enum CustomImeFlags
@ -103,6 +105,13 @@ namespace Avalonia.Android
}
private void _client_SelectionChanged(object sender, EventArgs e)
{
if (_inputConnection.IsInBatchEdit)
return;
OnSelectionChanged();
}
private void OnSelectionChanged()
{
var selection = Client.Selection;
@ -113,17 +122,67 @@ namespace Avalonia.Android
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 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)

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

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

Loading…
Cancel
Save