Skip to content

Commit

Permalink
Merge pull request #12270 from AvaloniaUI/android-text
Browse files Browse the repository at this point in the history
Android - Text Input fixes
  • Loading branch information
maxkatz6 authored Jul 23, 2023
2 parents 904b8ae + d43c72f commit 14f8914
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 50 deletions.
69 changes: 64 additions & 5 deletions src/Android/Avalonia.Android/AndroidInputMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ internal interface IAndroidInputMethod
public bool IsActive { get; }

public InputMethodManager IMM { get; }

void OnBatchEditedEnded();
}

enum CustomImeFlags
Expand Down Expand Up @@ -103,6 +105,13 @@ public void SetClient(TextInputMethodClient client)
}

private void _client_SelectionChanged(object sender, EventArgs e)
{
if (_inputConnection.IsInBatchEdit)
return;
OnSelectionChanged();
}

private void OnSelectionChanged()
{
var selection = Client.Selection;

Expand All @@ -113,17 +122,67 @@ private void _client_SelectionChanged(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 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)
Expand Down
108 changes: 63 additions & 45 deletions src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -447,19 +448,22 @@ internal class EditableWrapper : SpannableStringBuilder
{
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);
}

Expand All @@ -470,8 +474,6 @@ public override IEditable Replace(int start, int end, ICharSequence tb, int tbst
{
if (!IgnoreChange && start != end)
{
var text = tb.SubSequence(tbstart, tbend);

SelectSurroundingTextForDeletion(start, end);
}

Expand All @@ -490,8 +492,7 @@ internal class AvaloniaInputConnection : BaseInputConnection
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)
{
Expand All @@ -510,63 +511,87 @@ public AvaloniaInputConnection(TopLevelImpl toplevel, IAndroidInputMethod inputM

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)
Expand All @@ -575,11 +600,10 @@ public override bool DeleteSurroundingText(int beforeLength, int afterLength)
{
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);

Expand All @@ -588,13 +612,7 @@ public override bool DeleteSurroundingText(int beforeLength, int afterLength)
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)
Expand Down Expand Up @@ -630,7 +648,7 @@ public override ExtractedText GetExtractedText(ExtractedTextRequest request, [Ge
return null;
}

var selection = _selection;
var selection = _editable.CurrentSelection;

ExtractedText extract = new ExtractedText
{
Expand Down

0 comments on commit 14f8914

Please sign in to comment.