diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs
index bc2a2f3103..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,9 +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");
+ _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
new file mode 100644
index 0000000000..d921b2fa6c
--- /dev/null
+++ b/src/Web/Avalonia.Web.Blazor/Cursor.cs
@@ -0,0 +1,93 @@
+using Avalonia.Input;
+using Avalonia.Platform;
+
+namespace Avalonia.Web.Blazor
+{
+ public class CssCursor : ICursorImpl
+ {
+ public const string Default = "default";
+ 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,{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..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)
{
@@ -126,8 +127,16 @@ namespace Avalonia.Web.Blazor
public void SetCursor(ICursorImpl cursor)
{
- // nop
-
+ var cur = cursor as CssCursor;
+ var val = CssCursor.Default;
+ if (cur != null && cur.Value != null)
+ {
+ val = cur.Value;
+ }
+ if (_currentCursor != val)
+ {
+ SetCssCursor?.Invoke(val);
+ }
}
public IPopupImpl? CreatePopup()
@@ -146,6 +155,7 @@ namespace Avalonia.Web.Blazor
public IEnumerable