bombizombi 3 days ago
committed by GitHub
parent
commit
d0629acc74
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 13
      src/Browser/Avalonia.Browser/BrowserInputHandler.cs
  2. 40
      src/Browser/Avalonia.Browser/BrowserTextInputMethod.cs
  3. 12
      src/Browser/Avalonia.Browser/Interop/InputHelper.cs
  4. 1
      src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts
  5. 49
      src/Browser/Avalonia.Browser/webapp/modules/avalonia/input.ts

13
src/Browser/Avalonia.Browser/BrowserInputHandler.cs

@ -4,6 +4,7 @@ using System.Diagnostics;
using System.Runtime.InteropServices.JavaScript;
using Avalonia.Browser.Interop;
using Avalonia.Collections.Pooled;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Raw;
@ -40,7 +41,7 @@ internal class BrowserInputHandler
TextInputMethod = new BrowserTextInputMethod(this, container, inputElement);
InputPane = new BrowserInputPane();
InputHelper.SubscribeInputEvents(container, topLevelId);
InputHelper.SubscribeInputEvents(container, inputElement, topLevelId);
}
public BrowserTextInputMethod TextInputMethod { get; }
@ -229,6 +230,14 @@ internal class BrowserInputHandler
public bool OnKeyDown(string code, string key, int modifier)
{
//If we are processing beforeInput we must not process Backspace
//but onBeforeInput itself calls OnKeyBackspace, so filtering must be done at the same level.
//onBeforeInput will call RawKeyboardEvent.
if (key == "Backspace")
{
//return true; //why?
}
var handled = RawKeyboardEvent(RawKeyEventType.KeyDown, code, key, (RawInputModifiers)modifier);
if (!handled && key.Length == 1)
@ -303,7 +312,7 @@ internal class BrowserInputHandler
return false;
}
private bool RawKeyboardEvent(RawKeyEventType type, string domCode, string domKey, RawInputModifiers modifiers)
internal bool RawKeyboardEvent(RawKeyEventType type, string domCode, string domKey, RawInputModifiers modifiers)
{
if (_inputRoot is null)
return false;

40
src/Browser/Avalonia.Browser/BrowserTextInputMethod.cs

@ -1,6 +1,8 @@
using System;
using System.Runtime.InteropServices.JavaScript;
using Avalonia.Browser.Interop;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
namespace Avalonia.Browser;
@ -29,12 +31,14 @@ internal class BrowserTextInputMethod(
if (_client != null)
{
_client.SurroundingTextChanged -= SurroundingTextChanged;
_client.SelectionChanged -= SelectionCHanged;
_client.InputPaneActivationRequested -= InputPaneActivationRequested;
}
if (client != null)
{
client.SurroundingTextChanged += SurroundingTextChanged;
client.SelectionChanged += SelectionCHanged;
client.InputPaneActivationRequested += InputPaneActivationRequested;
}
@ -81,6 +85,17 @@ internal class BrowserTextInputMethod(
InputHelper.SetSurroundingText(_inputElement, surroundingText, selection.Start, selection.End);
}
}
private void SelectionCHanged(object? sender, EventArgs e)
{
if (_client != null)
{
var surroundingText = _client.SurroundingText ?? "";
var selection = _client.Selection;
InputHelper.SetSurroundingText(_inputElement, surroundingText, selection.Start, selection.End);
}
}
public void SetCursorRect(Rect rect)
{
@ -100,8 +115,11 @@ internal class BrowserTextInputMethod(
InputHelper.SetSurroundingText(_inputElement, "", 0, 0);
}
public void OnBeforeInput(string inputType, int start, int end)
public void OnBeforeInput(string inputType, int start, int end, string data)
{
bool handled;
/*
if (inputType != "deleteByComposition")
{
if (inputType == "deleteContentBackward")
@ -115,11 +133,29 @@ internal class BrowserTextInputMethod(
end = -1;
}
}
*/
if (start != -1 && end != -1 && _client != null)
{
_client.Selection = new TextSelection(start, end);
}
if ((inputType == "insertText") && (data.Length > 0))
{
handled = _inputHandler.RawTextEvent(data);
}
if (inputType == "deleteContentBackward")
{
handled = _inputHandler.RawKeyboardEvent(RawKeyEventType.KeyDown, "Backspace", "Backspace", (RawInputModifiers)0);
}
if ((inputType == "insertCompositionText") && (data.Length > 0))
{
handled = _inputHandler.RawTextEvent(data);
}
}
public void OnCompositionStart()
@ -150,7 +186,7 @@ internal class BrowserTextInputMethod(
if (data != null)
{
_inputHandler.RawTextEvent(data);
//_inputHandler.RawTextEvent(data); //handled on beforeinput event
}
}
}

12
src/Browser/Avalonia.Browser/Interop/InputHelper.cs

@ -12,7 +12,7 @@ internal static partial class InputHelper
return Task.CompletedTask;
}
public static Task<T> RedirectInputRetunAsync<T>(int topLevelId, Func<BrowserTopLevelImpl,T> handler, T @default)
public static Task<T> RedirectInputReturnAsync<T>(int topLevelId, Func<BrowserTopLevelImpl,T> handler, T @default)
{
if (BrowserTopLevelImpl.TryGetTopLevel(topLevelId) is { } topLevelImpl)
return Task.FromResult(handler(topLevelImpl));
@ -20,19 +20,19 @@ internal static partial class InputHelper
}
[JSImport("InputHelper.subscribeInputEvents", AvaloniaModule.MainModuleName)]
public static partial void SubscribeInputEvents(JSObject htmlElement, int topLevelId);
public static partial void SubscribeInputEvents(JSObject htmlElement, JSObject inputElement, int topLevelId);
[JSExport]
public static Task<bool> OnKeyDown(int topLevelId, string code, string key, int modifier) =>
RedirectInputRetunAsync(topLevelId, t => t.InputHandler.OnKeyDown(code, key, modifier), false);
RedirectInputReturnAsync(topLevelId, t => t.InputHandler.OnKeyDown(code, key, modifier), false);
[JSExport]
public static Task<bool> OnKeyUp(int topLevelId, string code, string key, int modifier) =>
RedirectInputRetunAsync(topLevelId, t => t.InputHandler.OnKeyUp(code, key, modifier), false);
RedirectInputReturnAsync(topLevelId, t => t.InputHandler.OnKeyUp(code, key, modifier), false);
[JSExport]
public static Task OnBeforeInput(int topLevelId, string inputType, int start, int end) =>
RedirectInputAsync(topLevelId, t => t.InputHandler.TextInputMethod.OnBeforeInput(inputType, start, end));
public static Task OnBeforeInput(int topLevelId, string inputType, int start, int end, string data) =>
RedirectInputAsync(topLevelId, t => t.InputHandler.TextInputMethod.OnBeforeInput(inputType, start, end, data));
[JSExport]
public static Task OnCompositionStart(int topLevelId) =>

1
src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts

@ -72,6 +72,7 @@ export class AvaloniaDOM {
// IME
const inputElement = document.createElement("input");
inputElement.contentEditable = "true";
inputElement.id = `inputElement${containerId}`;
inputElement.classList.add("avalonia-input-element");
inputElement.autocapitalize = "none";

49
src/Browser/Avalonia.Browser/webapp/modules/avalonia/input.ts

@ -73,6 +73,7 @@ export class InputHelper {
static clipboardState: ClipboardState = ClipboardState.None;
static resolveClipboard?: (value: readonly ReadableDataItem[]) => void;
static rejectClipboard?: (reason?: any) => void;
static enableIME: boolean = false; // hack, once turned on, stays on
public static initializeBackgroundHandlers() {
if (this.clipboardState !== ClipboardState.None) {
@ -302,10 +303,10 @@ export class InputHelper {
: new Uint8Array(await blob.arrayBuffer());
}
public static subscribeInputEvents(element: HTMLInputElement, topLevelId: number) {
const keySub = this.subscribeKeyEvents(element, topLevelId);
public static subscribeInputEvents(element: HTMLInputElement, inputElement: HTMLInputElement, topLevelId: number) {
const keySub = this.subscribeKeyEvents(element, inputElement, topLevelId);
const pointerSub = this.subscribePointerEvents(element, topLevelId);
const textSub = this.subscribeTextEvents(element, topLevelId);
const textSub = this.subscribeTextEvents(element, inputElement, topLevelId);
const dndSub = this.subscribeDropEvents(element, topLevelId);
const paneSub = this.subscribeKeyboardGeometryChange(element, topLevelId);
@ -318,11 +319,22 @@ export class InputHelper {
};
}
public static subscribeKeyEvents(element: HTMLInputElement, topLevelId: number) {
public static subscribeKeyEvents(element: HTMLInputElement, inputElement: HTMLInputElement, topLevelId: number) {
const keyDownHandler = (args: KeyboardEvent) => {
if (args.keyCode === 229) {
InputHelper.enableIME = true;
}
if (InputHelper.enableIME) {
if (args.key === "Backspace") {
return;
}
}
JsExports.InputHelper.OnKeyDown(topLevelId, args.code, args.key, this.getModifiers(args))
.then((handled: boolean) => {
if (!handled || this.clipboardState !== ClipboardState.Pending) {
// if (!handled || this.clipboardState !== ClipboardState.Pending) {
if (!handled) {
args.preventDefault();
}
});
@ -352,6 +364,7 @@ export class InputHelper {
public static subscribeTextEvents(
element: HTMLInputElement,
inputElement: HTMLInputElement,
topLevelId: number) {
const compositionStartHandler = (args: CompositionEvent) => {
JsExports.InputHelper.OnCompositionStart(topLevelId);
@ -359,6 +372,12 @@ export class InputHelper {
element.addEventListener("compositionstart", compositionStartHandler);
const beforeInputHandler = (args: InputEvent) => {
if (!InputHelper.enableIME) {
args.preventDefault(); // we modified the _input, so do not modify it twice
return; // do not process beforeInput
}
// this has no chance of working, since we have input type=text
const ranges = args.getTargetRanges();
let start = -1;
let end = -1;
@ -367,12 +386,21 @@ export class InputHelper {
end = ranges[0].endOffset;
}
if (args.inputType === "insertCompositionText") {
start = 2;
end = start + 2;
}
// wtf
// if (args.inputType === "insertCompositionText") {
// start = 2;
// end = start + 2;
// }
JsExports.InputHelper.OnBeforeInput(topLevelId, args.inputType, start, end);
// start and end here are uninitialized, <input type=text never sends targetRanges, see mdn
JsExports.InputHelper.OnBeforeInput(topLevelId, args.inputType, start, end, args.data ?? "");
// cancel events that were processed
if ((args.inputType === "insertText") ||
(args.inputType === "deleteContentBackward") ||
(args.inputType === "insertCompositionText")) {
args.preventDefault();
}
};
element.addEventListener("beforeinput", beforeInputHandler);
@ -389,6 +417,7 @@ export class InputHelper {
return () => {
element.removeEventListener("compositionstart", compositionStartHandler);
element.removeEventListener("beforeinput", beforeInputHandler);
element.removeEventListener("compositionupdate", compositionUpdateHandler);
element.removeEventListener("compositionend", compositionEndHandler);
};

Loading…
Cancel
Save