csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
342 lines
11 KiB
342 lines
11 KiB
using System.Collections.Concurrent;
|
|
using System.Threading;
|
|
using Android.OS;
|
|
using Android.Runtime;
|
|
using Android.Text;
|
|
using Android.Views;
|
|
using Android.Views.InputMethods;
|
|
using Avalonia.Android.Platform.SkiaPlatform;
|
|
using Avalonia.Input;
|
|
using Avalonia.Input.TextInput;
|
|
using Java.Lang;
|
|
|
|
namespace Avalonia.Android.Platform.Input
|
|
{
|
|
internal class AvaloniaInputConnection : Object, IInputConnection
|
|
{
|
|
private readonly TopLevelImpl _toplevel;
|
|
private readonly IAndroidInputMethod _inputMethod;
|
|
private readonly TextEditBuffer _editBuffer;
|
|
private readonly ConcurrentQueue<EditCommand> _commandQueue;
|
|
|
|
private int _batchLevel = 0;
|
|
|
|
public AvaloniaInputConnection(TopLevelImpl toplevel, IAndroidInputMethod inputMethod)
|
|
{
|
|
_toplevel = toplevel;
|
|
_inputMethod = inputMethod;
|
|
_editBuffer = new TextEditBuffer(_inputMethod, toplevel);
|
|
_commandQueue = new ConcurrentQueue<EditCommand>();
|
|
}
|
|
|
|
public int ExtractedTextToken { get; private set; }
|
|
|
|
public IAndroidInputMethod InputMethod => _inputMethod;
|
|
|
|
public TopLevelImpl Toplevel => _toplevel;
|
|
|
|
public bool IsInBatchEdit => _batchLevel > 0;
|
|
public bool IsInMonitorMode { get; private set; }
|
|
|
|
public Handler? Handler => null;
|
|
|
|
public TextEditBuffer EditBuffer => _editBuffer;
|
|
|
|
public bool IsInUpdate { get; set; }
|
|
|
|
internal void UpdateState()
|
|
{
|
|
var selection = _editBuffer.Selection;
|
|
|
|
if (IsInMonitorMode && InputMethod.Client is { } client)
|
|
{
|
|
InputMethod.IMM.UpdateExtractedText(InputMethod.View, ExtractedTextToken,
|
|
_editBuffer.ExtractedText);
|
|
}
|
|
|
|
var composition = _editBuffer.HasComposition ? _editBuffer.Composition!.Value : new TextSelection(-1, -1);
|
|
InputMethod.IMM.UpdateSelection(InputMethod.View, selection.Start, selection.End, composition.Start, composition.End);
|
|
}
|
|
|
|
public bool SetComposingRegion(int start, int end)
|
|
{
|
|
if (InputMethod.IsActive)
|
|
{
|
|
QueueCommand(new CompositionRegionCommand(start, end));
|
|
}
|
|
return InputMethod.IsActive;
|
|
}
|
|
|
|
public bool SetComposingText(ICharSequence? text, int newCursorPosition)
|
|
{
|
|
if (text is null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (InputMethod.IsActive)
|
|
{
|
|
var compositionText = text.SubSequence(0, text.Length());
|
|
QueueCommand(new CompositionTextCommand(compositionText, newCursorPosition));
|
|
}
|
|
|
|
return InputMethod.IsActive;
|
|
}
|
|
|
|
public bool SetSelection(int start, int end)
|
|
{
|
|
if (InputMethod.IsActive)
|
|
{
|
|
if (IsInUpdate)
|
|
new SelectionCommand(start, end).Apply(EditBuffer);
|
|
else
|
|
QueueCommand(new SelectionCommand(start, end));
|
|
}
|
|
|
|
return InputMethod.IsActive;
|
|
}
|
|
|
|
public bool BeginBatchEdit()
|
|
{
|
|
_batchLevel = Interlocked.Increment(ref _batchLevel);
|
|
return InputMethod.IsActive;
|
|
}
|
|
|
|
public bool EndBatchEdit()
|
|
{
|
|
_batchLevel = Interlocked.Decrement(ref _batchLevel);
|
|
|
|
if (!IsInBatchEdit)
|
|
{
|
|
IsInUpdate = true;
|
|
while (_commandQueue.TryDequeue(out var command))
|
|
{
|
|
command.Apply(_editBuffer);
|
|
}
|
|
IsInUpdate = false;
|
|
}
|
|
|
|
UpdateState();
|
|
return IsInBatchEdit;
|
|
}
|
|
|
|
public bool CommitText(ICharSequence? text, int newCursorPosition)
|
|
{
|
|
if (InputMethod.Client is null || text is null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (InputMethod.IsActive)
|
|
{
|
|
var committedText = text.SubSequence(0, text.Length());
|
|
QueueCommand(new CommitTextCommand(committedText, newCursorPosition));
|
|
}
|
|
|
|
return InputMethod.IsActive;
|
|
}
|
|
|
|
public bool DeleteSurroundingText(int beforeLength, int afterLength)
|
|
{
|
|
if (InputMethod.IsActive)
|
|
{
|
|
QueueCommand(new DeleteRegionCommand(beforeLength, afterLength));
|
|
}
|
|
|
|
return InputMethod.IsActive;
|
|
}
|
|
|
|
public bool PerformEditorAction([GeneratedEnum] ImeAction actionCode)
|
|
{
|
|
switch (actionCode)
|
|
{
|
|
case ImeAction.Done:
|
|
{
|
|
_inputMethod.IMM.HideSoftInputFromWindow(_inputMethod.View.WindowToken, HideSoftInputFlags.ImplicitOnly);
|
|
break;
|
|
}
|
|
case ImeAction.Next:
|
|
{
|
|
FocusManager.GetFocusManager(_toplevel.InputRoot)?
|
|
.TryMoveFocus(NavigationDirection.Next);
|
|
break;
|
|
}
|
|
}
|
|
|
|
var eventTime = SystemClock.UptimeMillis();
|
|
SendKeyEvent(new KeyEvent(eventTime,
|
|
eventTime,
|
|
KeyEventActions.Down,
|
|
Keycode.Enter,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
KeyEventFlags.SoftKeyboard | KeyEventFlags.KeepTouchMode | KeyEventFlags.EditorAction));
|
|
SendKeyEvent(new KeyEvent(eventTime,
|
|
eventTime,
|
|
KeyEventActions.Up,
|
|
Keycode.Enter,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
KeyEventFlags.SoftKeyboard | KeyEventFlags.KeepTouchMode | KeyEventFlags.EditorAction));
|
|
|
|
return InputMethod.IsActive;
|
|
}
|
|
|
|
public ExtractedText? GetExtractedText(ExtractedTextRequest? request, [GeneratedEnum] GetTextFlags flags)
|
|
{
|
|
IsInMonitorMode = ((int)flags & (int)TextExtractFlags.Monitor) != 0;
|
|
|
|
ExtractedTextToken = IsInMonitorMode ? request?.Token ?? 0 : ExtractedTextToken;
|
|
|
|
if (!_inputMethod.IsActive)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return _editBuffer.ExtractedText;
|
|
}
|
|
|
|
public bool PerformContextMenuAction(int id)
|
|
{
|
|
if (InputMethod.Client is not { } client)
|
|
return false;
|
|
|
|
switch (id)
|
|
{
|
|
case global::Android.Resource.Id.SelectAll:
|
|
client.ExecuteContextMenuAction(ContextMenuAction.SelectAll);
|
|
return true;
|
|
case global::Android.Resource.Id.Cut:
|
|
client.ExecuteContextMenuAction(ContextMenuAction.Cut);
|
|
return true;
|
|
case global::Android.Resource.Id.Copy:
|
|
client.ExecuteContextMenuAction(ContextMenuAction.Copy);
|
|
return true;
|
|
case global::Android.Resource.Id.Paste:
|
|
client.ExecuteContextMenuAction(ContextMenuAction.Paste);
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
return InputMethod.IsActive;
|
|
}
|
|
|
|
public bool ClearMetaKeyStates([GeneratedEnum] MetaKeyStates states)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public void CloseConnection()
|
|
{
|
|
_commandQueue.Clear();
|
|
_batchLevel = 0;
|
|
}
|
|
|
|
public bool CommitCompletion(CompletionInfo? text)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public bool CommitContent(InputContentInfo inputContentInfo, [GeneratedEnum] InputContentFlags flags, Bundle? opts)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public bool CommitCorrection(CorrectionInfo? correctionInfo)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public bool DeleteSurroundingTextInCodePoints(int beforeLength, int afterLength)
|
|
{
|
|
if (InputMethod.IsActive)
|
|
{
|
|
QueueCommand(new DeleteRegionInCodePointsCommand(beforeLength, afterLength));
|
|
}
|
|
|
|
return InputMethod.IsActive;
|
|
}
|
|
|
|
public bool FinishComposingText()
|
|
{
|
|
if (InputMethod.IsActive)
|
|
{
|
|
QueueCommand(new FinishComposingCommand());
|
|
}
|
|
|
|
return InputMethod.IsActive;
|
|
}
|
|
|
|
[return: GeneratedEnum]
|
|
public CapitalizationMode GetCursorCapsMode([GeneratedEnum] CapitalizationMode reqModes)
|
|
{
|
|
return TextUtils.GetCapsMode(_editBuffer.Text, _editBuffer.Selection.Start, reqModes);
|
|
}
|
|
|
|
public ICharSequence? GetSelectedTextFormatted([GeneratedEnum] GetTextFlags flags)
|
|
{
|
|
return new Java.Lang.String(_editBuffer.SelectedText ?? "");
|
|
}
|
|
|
|
public ICharSequence? GetTextAfterCursorFormatted(int n, [GeneratedEnum] GetTextFlags flags)
|
|
{
|
|
var end = Math.Min(_editBuffer.Selection.End, _editBuffer.Text.Length);
|
|
return SafeSubstring(_editBuffer.Text, end, Math.Min(n, _editBuffer.Text.Length - end));
|
|
}
|
|
|
|
public ICharSequence? GetTextBeforeCursorFormatted(int n, [GeneratedEnum] GetTextFlags flags)
|
|
{
|
|
var start = Math.Max(0, _editBuffer.Selection.Start - n);
|
|
var length = _editBuffer.Selection.Start - start;
|
|
return SafeSubstring(_editBuffer.Text, start, length);
|
|
}
|
|
|
|
public bool PerformPrivateCommand(string? action, Bundle? data)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public bool ReportFullscreenMode(bool enabled)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public bool RequestCursorUpdates(int cursorUpdateMode)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public bool SendKeyEvent(KeyEvent? e)
|
|
{
|
|
_inputMethod.View.DispatchKeyEvent(e);
|
|
|
|
return true;
|
|
}
|
|
|
|
private void QueueCommand(EditCommand command)
|
|
{
|
|
BeginBatchEdit();
|
|
|
|
try
|
|
{
|
|
_commandQueue.Enqueue(command);
|
|
}
|
|
finally
|
|
{
|
|
EndBatchEdit();
|
|
}
|
|
}
|
|
|
|
private static ICharSequence? SafeSubstring(string? text, int start, int length)
|
|
{
|
|
if (text == null || text.Length < start + length)
|
|
return null;
|
|
else
|
|
return new Java.Lang.String(text.Substring(start, length));
|
|
}
|
|
}
|
|
}
|
|
|