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