Browse Source

working input.

pull/9028/head
Dan Walmsley 4 years ago
parent
commit
b2b706ed20
  1. 1
      src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj
  2. 128
      src/Web/Avalonia.Web/AvaloniaView.cs
  3. 9
      src/Web/Avalonia.Web/BrowserTopLevelImpl.cs
  4. 21
      src/Web/Avalonia.Web/Interop/InputHelper.cs
  5. 27
      src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts

1
src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj

@ -14,6 +14,7 @@
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<RunAOTCompilation>true</RunAOTCompilation>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>full</TrimMode>
<WasmBuildNative>true</WasmBuildNative>
<InvariantGlobalization>true</InvariantGlobalization>
<WasmEnableSIMD>true</WasmEnableSIMD>

128
src/Web/Avalonia.Web/AvaloniaView.cs

@ -33,6 +33,7 @@ namespace Avalonia.Web
private bool _useGL;
private bool _inputElementFocused;
private ITextInputMethodClient? _client;
private static int _canvasCount;
public AvaloniaView(string divId)
@ -73,14 +74,17 @@ namespace Avalonia.Web
_topLevel.Renderer.Start();
InputHelper.SubscribeKeyboardEvents(
InputHelper.SubscribeKeyEvents(
_containerElement,
(code, key, modifier) => _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyDown, code, key, (RawInputModifiers)modifier),
(code, key, modifier) => _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyUp, code, key, (RawInputModifiers)modifier),
(args) => { return false; },
(args) => { return false; },
(args) => { return false; },
(args) => { return false; });
OnKeyDown,
OnKeyUp);
InputHelper.SubscribeTextEvents(
_inputElement,
OnTextInput,
OnCompositionStart,
OnCompositionUpdate,
OnCompositionEnd);
InputHelper.SubscribePointerEvents(_containerElement, OnPointerMove, OnPointerDown, OnPointerUp, OnWheel);
@ -235,6 +239,59 @@ namespace Avalonia.Web
return modifiers;
}
private bool OnKeyDown (string code, string key, int modifier)
{
return _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyDown, code, key, (RawInputModifiers)modifier);
}
private bool OnKeyUp(string code, string key, int modifier)
{
return _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyUp, code, key, (RawInputModifiers)modifier);
}
private bool OnTextInput (string type, string? data)
{
if(data == null || IsComposing)
{
return false;
}
return _topLevelImpl.RawTextEvent(data);
}
private bool OnCompositionStart (JSObject args)
{
if (_client == null)
return false;
_client.SetPreeditText(null);
IsComposing = true;
return false;
}
private bool OnCompositionUpdate(JSObject args)
{
if (_client == null)
return false;
_client.SetPreeditText(args.GetPropertyAsString("data"));
return false;
}
private bool OnCompositionEnd(JSObject args)
{
if (_client == null)
return false;
IsComposing = false;
_client.SetPreeditText(null);
_topLevelImpl.RawTextEvent(args.GetPropertyAsString("data")!);
return false;
}
private void OnRenderFrame()
{
if (_useGL && (_jsGlInfo == null))
@ -257,6 +314,8 @@ namespace Avalonia.Web
set => _topLevel.Content = value;
}
public bool IsComposing { get; private set; }
internal INativeControlHostImpl GetNativeControlHostImpl()
{
throw new NotImplementedException();
@ -314,13 +373,63 @@ namespace Avalonia.Web
}
}
private void HideIme()
{
InputHelper.HideElement(_inputElement);
InputHelper.FocusElement(_containerElement);
}
public void SetClient(ITextInputMethodClient? client)
{
Console.WriteLine("Set Client");
if (_client != null)
{
_client.SurroundingTextChanged -= SurroundingTextChanged;
}
if (client != null)
{
client.SurroundingTextChanged += SurroundingTextChanged;
}
InputHelper.ClearInputElement(_inputElement);
_client = client;
if (_client != null)
{
InputHelper.ShowElement(_inputElement);
_inputElementFocused = true;
InputHelper.FocusElement(_inputElement);
var surroundingText = _client.SurroundingText;
InputHelper.SetSurroundingText(_inputElement, surroundingText.Text, surroundingText.AnchorOffset, surroundingText.CursorOffset);
Console.WriteLine("Shown, focused and surrounded.");
}
else
{
_inputElementFocused = false;
HideIme();
}
}
private void SurroundingTextChanged(object? sender, EventArgs e)
{
if (_client != null)
{
var surroundingText = _client.SurroundingText;
InputHelper.SetSurroundingText(_inputElement, surroundingText.Text, surroundingText.AnchorOffset, surroundingText.CursorOffset);
}
}
public void SetCursorRect(Rect rect)
{
InputHelper.FocusElement(_inputElement);
InputHelper.SetBounds(_inputElement, (int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height, _client?.SurroundingText.CursorOffset ?? 0);
InputHelper.FocusElement(_inputElement);
}
public void SetOptions(TextInputOptions options)
@ -329,7 +438,8 @@ namespace Avalonia.Web
public void Reset()
{
InputHelper.ClearInputElement(_inputElement);
InputHelper.SetSurroundingText(_inputElement, "", 0, 0);
}
}
}

9
src/Web/Avalonia.Web/BrowserTopLevelImpl.cs

@ -141,12 +141,17 @@ namespace Avalonia.Web
return false;
}
public void RawTextEvent(string text)
public bool RawTextEvent(string text)
{
if (_inputRoot is { })
{
Input?.Invoke(new RawTextInputEventArgs(KeyboardDevice, Timestamp, _inputRoot, text));
var args = new RawTextInputEventArgs(KeyboardDevice, Timestamp, _inputRoot, text);
Input?.Invoke(args);
return args.Handled;
}
return false;
}
public void Dispose()

21
src/Web/Avalonia.Web/Interop/InputHelper.cs

@ -1,20 +1,26 @@
using System;
using System.Runtime.InteropServices.JavaScript;
using System.Threading.Tasks;
using static System.Net.Mime.MediaTypeNames;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace Avalonia.Web.Interop;
internal static partial class InputHelper
{
[JSImport("InputHelper.subscribeKeyboardEvents", "avalonia.ts")]
public static partial void SubscribeKeyboardEvents(
[JSImport("InputHelper.subscribeKeyEvents", "avalonia.ts")]
public static partial void SubscribeKeyEvents(
JSObject htmlElement,
[JSMarshalAs<JSType.Function<JSType.String, JSType.String, JSType.Number, JSType.Boolean>>]
Func<string, string, int, bool> keyDown,
[JSMarshalAs<JSType.Function<JSType.String, JSType.String, JSType.Number, JSType.Boolean>>]
Func<string, string, int, bool> keyUp,
[JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
Func<JSObject, bool> onInput,
Func<string, string, int, bool> keyUp);
[JSImport("InputHelper.subscribeTextEvents", "avalonia.ts")]
public static partial void SubscribeTextEvents(
JSObject htmlElement,
[JSMarshalAs<JSType.Function<JSType.String, JSType.String, JSType.Boolean>>]
Func<string, string?, bool> onInput,
[JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
Func<JSObject, bool> onCompositionStart,
[JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
@ -60,6 +66,11 @@ internal static partial class InputHelper
[JSImport("InputHelper.show", "avalonia.ts")]
public static partial void ShowElement(JSObject htmlElement);
[JSImport("InputHelper.setSurroundingText", "avalonia.ts")]
public static partial void SetSurroundingText(JSObject htmlElement, string text, int start, int end);
[JSImport("InputHelper.setBounds", "avalonia.ts")]
public static partial void SetBounds(JSObject htmlElement, int x, int y, int width, int height, int caret);
[JSImport("navigator.clipboard.readText")]
public static partial Task<string> ReadClipboardTextAsync();

27
src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts

@ -21,11 +21,11 @@ enum RawInputModifiers
}
export class InputHelper {
public static subscribeKeyboardEvents(
public static subscribeKeyEvents(
element: HTMLInputElement,
keyDownCallback: (code: string, key: string, modifiers: RawInputModifiers) => boolean,
keyUpCallback: (code: string, key: string, modifiers: RawInputModifiers) => boolean,
inputCallback: (args: InputEvent) => boolean,
inputCallback: (type: string, data: string|null) => boolean,
compositionStartCallback: (args: CompositionEvent) => boolean,
compositionUpdateCallback: (args: CompositionEvent) => boolean,
compositionEndCallback: (args: CompositionEvent) => boolean)
@ -42,13 +42,27 @@ export class InputHelper {
args.preventDefault();
}
};
element.addEventListener("keyup", keyUpHandler);
return () => {
element.removeEventListener("keydown", keyDownHandler);
element.removeEventListener("keyup", keyUpHandler);
};
}
public static subscribeTextEvents(
element: HTMLInputElement,
inputCallback: (type: string, data: string | null) => boolean,
compositionStartCallback: (args: CompositionEvent) => boolean,
compositionUpdateCallback: (args: CompositionEvent) => boolean,
compositionEndCallback: (args: CompositionEvent) => boolean) {
const inputHandler = (args: Event) => {
var inputEvent = args as InputEvent;
// todo check cast
if (inputCallback(inputEvent)) {
if (inputCallback(inputEvent.type, inputEvent.data)) {
args.preventDefault();
}
};
@ -76,16 +90,11 @@ export class InputHelper {
element.addEventListener("compositionend", compositionEndHandler);
return () => {
element.removeEventListener("keydown", keyDownHandler);
element.removeEventListener("keyup", keyUpHandler);
element.removeEventListener("input", inputHandler);
element.removeEventListener("compositionstart", compositionStartHandler);
element.removeEventListener("compositionupdate", compositionUpdateHandler);
element.removeEventListener("compositionend", compositionEndHandler);
};
}
public static subscribePointerEvents(

Loading…
Cancel
Save