From 57ac0cc7fb4e2476ab03bb332316fab4b5c4c545 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 19 Aug 2025 12:17:01 +0500 Subject: [PATCH] Added PluggableTextInputMethod API --- .../Input/TextInput/InputMethodManager.cs | 47 ++++++++++----- .../TextInput/PluggableTextInputMethod.cs | 57 +++++++++++++++++++ 2 files changed, 89 insertions(+), 15 deletions(-) create mode 100644 src/Avalonia.Base/Input/TextInput/PluggableTextInputMethod.cs diff --git a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs index ae3a8bc6a1..3e2531e3c7 100644 --- a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs +++ b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs @@ -88,7 +88,7 @@ namespace Avalonia.Input.TextInput { if (ReferenceEquals(obj.Sender, _focusedElement)) { - TryFindAndApplyClient(); + RediscoverInputMethodAndClient(); } } @@ -132,42 +132,59 @@ namespace Avalonia.Input.TextInput InputMethod.AddTextInputMethodClientRequeryRequestedHandler(_visualRoot, TextInputMethodClientRequeryRequested); - var inputMethod = ((element as Visual)?.VisualRoot as ITextInputMethodRoot)?.InputMethod; - - if (_im != inputMethod) - { - _im?.SetClient(null); - } + RediscoverInputMethodAndClient(); + } + + void RediscoverInputMethodAndClient() + { + var (inputMethod, inputClient) = FindInputMethodAndClient(); + + // Reset the previous input method and our state on input method change + if (_im != inputMethod) + Client = null; _im = inputMethod; - - TryFindAndApplyClient(); + Client = inputClient; } private void TextInputMethodClientRequeryRequested(object? sender, RoutedEventArgs e) { if (_im != null) - TryFindAndApplyClient(); + RediscoverInputMethodAndClient(); } - - private void TryFindAndApplyClient() + + private (ITextInputMethodImpl? im, TextInputMethodClient? client) FindInputMethodAndClient() { if (_focusedElement is not InputElement focused || _im == null || !InputMethod.GetIsInputMethodEnabled(focused)) { - Client = null; - return; + // Input method system is disabled by focused element + return (null, null); } + // Attempt to get a user-provided input method + var imQuery = new PluggableTextInputMethodRequestedEventArgs() + { + RoutedEvent = PluggableTextInputMethod.TextInputMethodRequestedEvent + }; + _focusedElement.RaiseEvent(imQuery); + + // Fall back to the system provided one, if any + var im = imQuery.InputMethod?.Adapter + ?? (focused.VisualRoot as ITextInputMethodRoot)?.InputMethod; + + if (im == null) + return (null, null); + var clientQuery = new TextInputMethodClientRequestedEventArgs { RoutedEvent = InputElement.TextInputMethodClientRequestedEvent }; _focusedElement.RaiseEvent(clientQuery); - Client = clientQuery.Client; + return (im, clientQuery.Client); } } } diff --git a/src/Avalonia.Base/Input/TextInput/PluggableTextInputMethod.cs b/src/Avalonia.Base/Input/TextInput/PluggableTextInputMethod.cs new file mode 100644 index 0000000000..2cd72b203f --- /dev/null +++ b/src/Avalonia.Base/Input/TextInput/PluggableTextInputMethod.cs @@ -0,0 +1,57 @@ +using Avalonia.Interactivity; + +namespace Avalonia.Input.TextInput; + +public abstract class PluggableTextInputMethod +{ + internal TextInputMethodAdapter Adapter { get; } + + public PluggableTextInputMethod() + { + Adapter = new(this); + } + + public virtual void SetClient(TextInputMethodClient? client) + { + } + + public virtual void SetOptions(TextInputOptions options) + { + } + + internal class TextInputMethodAdapter(PluggableTextInputMethod method) : ITextInputMethodImpl + { + public void SetClient(TextInputMethodClient? client) + { + method.SetClient(client); + } + + public void SetCursorRect(Rect rect) + { + // No-op + } + + public void SetOptions(TextInputOptions options) => method.SetOptions(options); + + public void Reset() + { + // Implementations should be subscribing to reset event manually + } + } + + /// + /// Defines the event. + /// + public static readonly RoutedEvent TextInputMethodRequestedEvent = + RoutedEvent.Register( + nameof(PluggableTextInputMethodRequestedEventArgs), + RoutingStrategies.Tunnel | RoutingStrategies.Bubble); +} + +public class PluggableTextInputMethodRequestedEventArgs : RoutedEventArgs +{ + /// + /// Set this property to a valid pluggable text input method to enable its usage with the input system + /// + public PluggableTextInputMethod? InputMethod { get; set; } +} \ No newline at end of file