From d8c89df26b850eb57cf29e7b3c993cc2993554eb Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Sat, 1 Jan 2022 10:32:24 +0300 Subject: [PATCH 1/2] [WASM] Cursors --- .../Avalonia.Web.Blazor/AvaloniaView.razor.cs | 1 + src/Web/Avalonia.Web.Blazor/Cursor.cs | 92 +++++++++++++++++++ .../RazorViewTopLevelImpl.cs | 9 +- src/Web/Avalonia.Web.Blazor/WinStubs.cs | 21 ----- .../Avalonia.Web.Blazor/WindowingPlatform.cs | 2 +- 5 files changed, 101 insertions(+), 24 deletions(-) create mode 100644 src/Web/Avalonia.Web.Blazor/Cursor.cs diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs index bc2a2f3103..a682a25915 100644 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs +++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs @@ -257,6 +257,7 @@ namespace Avalonia.Web.Blazor _inputHelper.Hide(); _inputHelper.SetCursor("default"); + _topLevelImpl.SetCssCursor = _inputHelper.SetCursor; Console.WriteLine("starting html canvas setup"); _interop = await SKHtmlCanvasInterop.ImportAsync(Js, _htmlCanvas, OnRenderFrame); diff --git a/src/Web/Avalonia.Web.Blazor/Cursor.cs b/src/Web/Avalonia.Web.Blazor/Cursor.cs new file mode 100644 index 0000000000..cd9010dfc2 --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/Cursor.cs @@ -0,0 +1,92 @@ +using Avalonia.Input; +using Avalonia.Platform; + +namespace Avalonia.Web.Blazor +{ + public class CssCursor : ICursorImpl + { + public string? Value { get; set; } + + public CssCursor(StandardCursorType type) + { + Value = ToKeyword(type); + } + + /// + /// Create a cursor from base64 image + /// + public CssCursor(string base64, string format, PixelPoint hotspot, StandardCursorType fallback) + { + Value = $"url(data:image/{format},{base64}) {hotspot.X} {hotspot.Y}, {ToKeyword(fallback)}"; + } + + /// + /// Create a cursor from url to *.cur file. + /// + public CssCursor(string url, StandardCursorType fallback) + { + Value = $"url('{url}'), {ToKeyword(fallback)}"; + } + + /// + /// Create a cursor from png/svg and hotspot position + /// + public CssCursor(string url, PixelPoint hotSpot, StandardCursorType fallback) + { + Value = $"url('{url}') {hotSpot.X} {hotSpot.Y}, {ToKeyword(fallback)}"; + } + + private static string ToKeyword(StandardCursorType type) => type switch + { + StandardCursorType.Hand => "pointer", + StandardCursorType.Cross => "crosshair", + StandardCursorType.Help => "help", + StandardCursorType.Ibeam => "text", + StandardCursorType.No => "not-allowed", + StandardCursorType.None => "none", + StandardCursorType.Wait => "progress", + StandardCursorType.AppStarting => "wait", + + StandardCursorType.DragMove => "move", + StandardCursorType.DragCopy => "copy", + StandardCursorType.DragLink => "alias", + + StandardCursorType.UpArrow => "default",/*not found matching one*/ + StandardCursorType.SizeWestEast => "ew-resize", + StandardCursorType.SizeNorthSouth => "ns-resize", + StandardCursorType.SizeAll => "move", + + StandardCursorType.TopSide => "n-resize", + StandardCursorType.BottomSide => "s-resize", + StandardCursorType.LeftSide => "w-resize", + StandardCursorType.RightSide => "e-resize", + StandardCursorType.TopLeftCorner => "nw-resize", + StandardCursorType.TopRightCorner => "ne-resize", + StandardCursorType.BottomLeftCorner => "sw-resize", + StandardCursorType.BottomRightCorner => "se-resize", + + _ => "default", + }; + + public void Dispose() {} + } + + internal class CssCursorFactory : ICursorFactory + { + public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + { + using var imageStream = new MemoryStream(); + cursor.Save(imageStream); + + //not memory optimized because CryptoStream with ToBase64Transform is not supported in the browser. + var base64String = Convert.ToBase64String(imageStream.ToArray()); + return new CssCursor(base64String, "png", hotSpot, StandardCursorType.Arrow); + } + + ICursorImpl ICursorFactory.GetCursor(StandardCursorType cursorType) + { + return new CssCursor(cursorType); + } + } +} + diff --git a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs index acd23a98b0..1b1fca7e26 100644 --- a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs +++ b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs @@ -126,8 +126,12 @@ namespace Avalonia.Web.Blazor public void SetCursor(ICursorImpl cursor) { - // nop - + var cur = cursor as CssCursor; + if (cur == null || cur.Value == null) + { + throw new NotSupportedException(); + } + SetCssCursor?.Invoke(cur.Value); } public IPopupImpl? CreatePopup() @@ -146,6 +150,7 @@ namespace Avalonia.Web.Blazor public IEnumerable Surfaces => new object[] { _currentSurface! }; + public Action? SetCssCursor { get; set; } public Action? Input { get; set; } public Action? Paint { get; set; } public Action? Resized { get; set; } diff --git a/src/Web/Avalonia.Web.Blazor/WinStubs.cs b/src/Web/Avalonia.Web.Blazor/WinStubs.cs index 13d83a9ee3..7b2bff6bfd 100644 --- a/src/Web/Avalonia.Web.Blazor/WinStubs.cs +++ b/src/Web/Avalonia.Web.Blazor/WinStubs.cs @@ -23,27 +23,6 @@ namespace Avalonia.Web.Blazor public Task GetDataAsync(string format) => Task.FromResult(new ()); } - internal class CursorStub : ICursorImpl - { - public void Dispose() - { - - } - } - - internal class CursorFactoryStub : ICursorFactory - { - public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) - { - return new CursorStub(); - } - - ICursorImpl ICursorFactory.GetCursor(StandardCursorType cursorType) - { - return new CursorStub(); - } - } - internal class IconLoaderStub : IPlatformIconLoader { private class IconStub : IWindowIconImpl diff --git a/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs b/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs index 979e66a785..ac970d067f 100644 --- a/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs +++ b/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs @@ -35,7 +35,7 @@ namespace Avalonia.Web.Blazor s_keyboard = new KeyboardDevice(); AvaloniaLocator.CurrentMutable .Bind().ToSingleton() - .Bind().ToSingleton() + .Bind().ToSingleton() .Bind().ToConstant(s_keyboard) .Bind().ToConstant(instance) .Bind().ToConstant(instance) From 100af8ffe307ba7664c3f8c0adfe695f62fab4ce Mon Sep 17 00:00:00 2001 From: Sergey Mikolaytis Date: Sat, 1 Jan 2022 12:46:26 +0300 Subject: [PATCH 2/2] [WASM] fix cursors. works fine --- src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs | 6 ++++-- src/Web/Avalonia.Web.Blazor/Cursor.cs | 5 +++-- src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs | 11 ++++++++--- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs index a682a25915..4b0ada1f27 100644 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs +++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs @@ -22,6 +22,7 @@ namespace Avalonia.Web.Blazor private DpiWatcherInterop _dpiWatcher = null!; private SKHtmlCanvasInterop.GLInfo? _jsGlInfo = null!; private InputHelperInterop _inputHelper = null!; + private InputHelperInterop _canvasHelper = null!; private ElementReference _htmlCanvas; private ElementReference _inputElement; private double _dpi; @@ -254,10 +255,11 @@ namespace Avalonia.Web.Blazor Threading.Dispatcher.UIThread.Post(async () => { _inputHelper = await InputHelperInterop.ImportAsync(Js, _inputElement); + _canvasHelper = await InputHelperInterop.ImportAsync(Js, _htmlCanvas); _inputHelper.Hide(); - _inputHelper.SetCursor("default"); - _topLevelImpl.SetCssCursor = _inputHelper.SetCursor; + _canvasHelper.SetCursor("default"); + _topLevelImpl.SetCssCursor = _canvasHelper.SetCursor; Console.WriteLine("starting html canvas setup"); _interop = await SKHtmlCanvasInterop.ImportAsync(Js, _htmlCanvas, OnRenderFrame); diff --git a/src/Web/Avalonia.Web.Blazor/Cursor.cs b/src/Web/Avalonia.Web.Blazor/Cursor.cs index cd9010dfc2..d921b2fa6c 100644 --- a/src/Web/Avalonia.Web.Blazor/Cursor.cs +++ b/src/Web/Avalonia.Web.Blazor/Cursor.cs @@ -5,6 +5,7 @@ namespace Avalonia.Web.Blazor { public class CssCursor : ICursorImpl { + public const string Default = "default"; public string? Value { get; set; } public CssCursor(StandardCursorType type) @@ -17,7 +18,7 @@ namespace Avalonia.Web.Blazor /// public CssCursor(string base64, string format, PixelPoint hotspot, StandardCursorType fallback) { - Value = $"url(data:image/{format},{base64}) {hotspot.X} {hotspot.Y}, {ToKeyword(fallback)}"; + Value = $"url(\"data:image/{format};base64,{base64}\") {hotspot.X} {hotspot.Y}, {ToKeyword(fallback)}"; } /// @@ -65,7 +66,7 @@ namespace Avalonia.Web.Blazor StandardCursorType.BottomLeftCorner => "sw-resize", StandardCursorType.BottomRightCorner => "se-resize", - _ => "default", + _ => Default, }; public void Dispose() {} diff --git a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs index 1b1fca7e26..ac5f5044d8 100644 --- a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs +++ b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs @@ -21,6 +21,7 @@ namespace Avalonia.Web.Blazor private readonly Stopwatch _sw = Stopwatch.StartNew(); private readonly ITextInputMethodImpl _textInputMethod; private readonly TouchDevice _touchDevice; + private string _currentCursor = CssCursor.Default; public RazorViewTopLevelImpl(ITextInputMethodImpl textInputMethod) { @@ -127,11 +128,15 @@ namespace Avalonia.Web.Blazor public void SetCursor(ICursorImpl cursor) { var cur = cursor as CssCursor; - if (cur == null || cur.Value == null) + var val = CssCursor.Default; + if (cur != null && cur.Value != null) { - throw new NotSupportedException(); + val = cur.Value; + } + if (_currentCursor != val) + { + SetCssCursor?.Invoke(val); } - SetCssCursor?.Invoke(cur.Value); } public IPopupImpl? CreatePopup()