diff --git a/Avalonia.sln b/Avalonia.sln index 461de8530b..e2f04ddc35 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -522,6 +522,7 @@ Global {3B8519C1-2F51-4F12-A348-120AB91D4532}.Release|Any CPU.Build.0 = Release|Any CPU {C90FE60B-B01E-4F35-91D6-379D6966030F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C90FE60B-B01E-4F35-91D6-379D6966030F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C90FE60B-B01E-4F35-91D6-379D6966030F}.Debug|Any CPU.Deploy.0 = Debug|Any CPU {C90FE60B-B01E-4F35-91D6-379D6966030F}.Release|Any CPU.ActiveCfg = Release|Any CPU {C90FE60B-B01E-4F35-91D6-379D6966030F}.Release|Any CPU.Build.0 = Release|Any CPU {FED9A71D-00D7-4F40-A9E4-1229EEA28EEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU diff --git a/samples/MobileSandbox/MainView.xaml b/samples/MobileSandbox/MainView.xaml index 9c46f3eae9..5f9f41f3a9 100644 --- a/samples/MobileSandbox/MainView.xaml +++ b/samples/MobileSandbox/MainView.xaml @@ -3,6 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs index 040043e618..b6adbde738 100644 --- a/src/Android/Avalonia.Android/AndroidInputMethod.cs +++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs @@ -1,6 +1,7 @@ using System; using Android.Content; using Android.Runtime; +using Android.Text; using Android.Views; using Android.Views.InputMethods; using Avalonia.Android.Platform.SkiaPlatform; @@ -62,22 +63,21 @@ namespace Avalonia.Android public void Reset() { - _imm.RestartInput(_host); + } public void SetClient(ITextInputMethodClient client) { - if(client is null) - { - _inputConnection?.SetComposingText("", 0); - } - if (_client != null) { _client.SurroundingTextChanged -= SurroundingTextChanged; } - Reset(); + if(_inputConnection != null) + { + _inputConnection.ComposingText = null; + _inputConnection.ComposingRegion = default; + } _client = client; @@ -87,23 +87,31 @@ namespace Avalonia.Android _host.RequestFocus(); + _imm.RestartInput(View); + _imm.ShowSoftInput(_host, ShowFlags.Implicit); - } - else - { - _imm.HideSoftInputFromWindow(_host.WindowToken, HideSoftInputFlags.None); + + var surroundingText = Client.SurroundingText; + + _imm.UpdateSelection(_host, surroundingText.AnchorOffset, surroundingText.CursorOffset, surroundingText.AnchorOffset, surroundingText.CursorOffset); } } private void SurroundingTextChanged(object sender, EventArgs e) { - if (IsActive) + if (IsActive && _inputConnection != null) { var surroundingText = Client.SurroundingText; _inputConnection.SurroundingText = surroundingText; _imm.UpdateSelection(_host, surroundingText.AnchorOffset, surroundingText.CursorOffset, surroundingText.AnchorOffset, surroundingText.CursorOffset); + + if (_inputConnection.ComposingText != null && !_inputConnection.IsCommiting && surroundingText.AnchorOffset == surroundingText.CursorOffset) + { + _inputConnection.CommitText(_inputConnection.ComposingText, 0); + _inputConnection.SetSelection(surroundingText.AnchorOffset, surroundingText.CursorOffset); + } } } @@ -153,10 +161,20 @@ namespace Avalonia.Android return _inputConnection; }); } + } - private void RestoreSoftKeyboard(object sender, PointerReleasedEventArgs e) + public readonly struct ComposingRegion + { + private readonly int _start = -1; + private readonly int _end = -1; + + public ComposingRegion(int start, int end) { - _imm.ShowSoftInput(_host, ShowFlags.Implicit); + _start = start; + _end = end; } + + public int Start => _start; + public int End => _end; } } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 4e972d504a..5f53eb36c8 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -3,7 +3,9 @@ using System.Collections.Generic; using Android.App; using Android.Content; using Android.Graphics; +using Android.OS; using Android.Runtime; +using Android.Text; using Android.Views; using Android.Views.InputMethods; using Avalonia.Android.OpenGL; @@ -23,6 +25,7 @@ using Avalonia.Platform.Storage; using Avalonia.Rendering; using Avalonia.Rendering.Composition; using Java.Lang; +using Math = System.Math; namespace Avalonia.Android.Platform.SkiaPlatform { @@ -276,8 +279,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform { throw new NotImplementedException(); } - - } internal class AvaloniaInputConnection : BaseInputConnection @@ -293,11 +294,12 @@ namespace Avalonia.Android.Platform.SkiaPlatform public TextInputMethodSurroundingText SurroundingText { get; set; } - public string ComposingText { get; private set; } + public string ComposingText { get; internal set; } - public ComposingRegion ComposingRegion { get; private set; } + public ComposingRegion? ComposingRegion { get; internal set; } - public bool IsComposing { get; private set; } + public bool IsComposing => !string.IsNullOrEmpty(ComposingText); + public bool IsCommiting { get; private set; } public override bool SetComposingRegion(int start, int end) { @@ -314,8 +316,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform ComposingText = composingText; - IsComposing = true; - _inputMethod.Client?.SetPreeditText(ComposingText); return base.SetComposingText(text, newCursorPosition); @@ -323,20 +323,25 @@ namespace Avalonia.Android.Platform.SkiaPlatform public override bool FinishComposingText() { - IsComposing = false; - - ComposingRegion = new ComposingRegion(SurroundingText.CursorOffset, SurroundingText.CursorOffset); + if (!string.IsNullOrEmpty(ComposingText)) + { + CommitText(ComposingText, ComposingText.Length); + } + else + { + ComposingRegion = new ComposingRegion(SurroundingText.CursorOffset, SurroundingText.CursorOffset); + } return base.FinishComposingText(); } public override ICharSequence GetTextBeforeCursorFormatted(int length, [GeneratedEnum] GetTextFlags flags) { - if (!string.IsNullOrEmpty(SurroundingText.Text)) + if (!string.IsNullOrEmpty(SurroundingText.Text) && length > 0) { var start = System.Math.Max(SurroundingText.CursorOffset - length, 0); - var end = System.Math.Min(start + length, SurroundingText.CursorOffset); + var end = System.Math.Min(start + length - 1, SurroundingText.CursorOffset); var text = SurroundingText.Text.Substring(start, end - start); @@ -368,11 +373,31 @@ namespace Avalonia.Android.Platform.SkiaPlatform public override bool CommitText(ICharSequence text, int newCursorPosition) { + IsCommiting = true; var committedText = text.ToString(); _inputMethod.Client.SetPreeditText(null); - _inputMethod.Client.SelectInSurroundingText(ComposingRegion.Start, ComposingRegion.End); + int? start, end; + + if(SurroundingText.CursorOffset != SurroundingText.AnchorOffset) + { + start = Math.Min(SurroundingText.CursorOffset, SurroundingText.AnchorOffset); + end = Math.Max(SurroundingText.CursorOffset, SurroundingText.AnchorOffset); + } + else if (ComposingRegion != null) + { + start = ComposingRegion?.Start; + end = ComposingRegion?.End; + + ComposingRegion = null; + } + else + { + start = end = _inputMethod.Client.SurroundingText.CursorOffset; + } + + _inputMethod.Client.SelectInSurroundingText((int)start, (int)end); var time = DateTime.Now.TimeOfDay; @@ -380,15 +405,29 @@ namespace Avalonia.Android.Platform.SkiaPlatform _topLevel.Input(rawTextEvent); + ComposingText = null; + + ComposingRegion = new ComposingRegion(newCursorPosition, newCursorPosition); + return base.CommitText(text, newCursorPosition); } public override bool DeleteSurroundingText(int beforeLength, int afterLength) { - _inputMethod.Client.SelectInSurroundingText(beforeLength, afterLength); + var surroundingText = _inputMethod.Client.SurroundingText; + + var selectionStart = surroundingText.CursorOffset; + + _inputMethod.Client.SelectInSurroundingText(selectionStart - beforeLength, selectionStart + afterLength); _inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel)); + surroundingText = _inputMethod.Client.SurroundingText; + + selectionStart = surroundingText.CursorOffset; + + ComposingRegion = new ComposingRegion(selectionStart, selectionStart); + return base.DeleteSurroundingText(beforeLength, afterLength); } @@ -396,22 +435,23 @@ namespace Avalonia.Android.Platform.SkiaPlatform { _inputMethod.Client.SelectInSurroundingText(start, end); + ComposingRegion = new ComposingRegion(start, end); + return base.SetSelection(start, end); } - } - - public readonly struct ComposingRegion - { - private readonly int _start = -1; - private readonly int _end = -1; - public ComposingRegion(int start, int end) + public override bool PerformEditorAction([GeneratedEnum] ImeAction actionCode) { - _start = start; - _end = end; - } + switch (actionCode) + { + case ImeAction.Done: + { + _inputMethod.IMM.HideSoftInputFromWindow(_inputMethod.View.WindowToken, HideSoftInputFlags.ImplicitOnly); + break; + } + } - public int Start => _start; - public int End => _end; + return base.PerformEditorAction(actionCode); + } } } diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs index 4cae700c0a..57f6469a42 100644 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs @@ -63,7 +63,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers { var rawTextEvent = new RawTextInputEventArgs( AndroidKeyboardDevice.Instance, - Convert.ToUInt32(e.EventTime), + Convert.ToUInt64(e.EventTime), _view.InputRoot, unicodeTextInput ?? Convert.ToChar(e.UnicodeChar).ToString() ); diff --git a/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs b/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs index 325b6dd0ef..325d745782 100644 --- a/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs +++ b/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs @@ -29,6 +29,7 @@ namespace Avalonia.Input.TextInput /// Sets the non-committed input string /// void SetPreeditText(string? text); + /// /// Indicates if text input client is capable of providing the text around the cursor /// diff --git a/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs b/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs index 7ab67ea34d..fb8e699d8e 100644 --- a/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs +++ b/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs @@ -140,7 +140,7 @@ namespace Avalonia.Media.TextFormatting } } - return length; + return Math.Min(length, text.Length); } } } diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index adf0569551..cc1fa6c513 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -9,6 +9,7 @@ using Avalonia.VisualTree; using Avalonia.Layout; using Avalonia.Media.Immutable; using Avalonia.Controls.Documents; +using Avalonia.Input.TextInput; namespace Avalonia.Controls.Presenters { @@ -514,7 +515,12 @@ namespace Avalonia.Controls.Presenters { if (!string.IsNullOrEmpty(_preeditText)) { - var text = _text?.Substring(0, _caretIndex) + _preeditText + _text?.Substring(_caretIndex); + if (string.IsNullOrEmpty(_text) || _caretIndex > _text.Length) + { + return _preeditText; + } + + var text = _text.Substring(0, _caretIndex) + _preeditText + _text.Substring(_caretIndex); return text; } @@ -868,28 +874,11 @@ namespace Avalonia.Controls.Presenters if (string.IsNullOrEmpty(newValue)) { - if (!string.IsNullOrEmpty(oldValue)) - { - var textPosition = _compositionStartHit.FirstCharacterIndex + _compositionStartHit.TrailingLength + newValue?.Length ?? 0; - - var characterHit = GetCharacterHitFromTextPosition(textPosition); - - UpdateCaret(characterHit, true); - } - - _compositionStartHit = new CharacterHit(-1); + UpdateCaret(_lastCharacterHit); } else { - if (_compositionStartHit.FirstCharacterIndex == -1) - { - _compositionStartHit = _lastCharacterHit; - } - } - - if (_compositionStartHit.FirstCharacterIndex != -1) - { - var textPosition = _compositionStartHit.FirstCharacterIndex + _compositionStartHit.TrailingLength + newValue?.Length ?? 0; + var textPosition = _caretIndex + newValue?.Length ?? 0; var characterHit = GetCharacterHitFromTextPosition(textPosition); diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 85c1c9a9d1..d5b45398e7 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -1212,7 +1212,7 @@ namespace Avalonia.Controls protected override void OnPointerPressed(PointerPressedEventArgs e) { - if (_presenter == null || !string.IsNullOrEmpty(_presenter.PreeditText)) + if (_presenter == null ) { return; }