Skip to content

Commit ba9c0b3

Browse files
authored
Merge pull request #7295 from Mikolaytis/WasmCursors
[WASM] Implement Cursors API
2 parents 9f075ae + 1f88245 commit ba9c0b3

File tree

5 files changed

+110
-25
lines changed

5 files changed

+110
-25
lines changed

src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public partial class AvaloniaView : ITextInputMethodImpl
2222
private DpiWatcherInterop _dpiWatcher = null!;
2323
private SKHtmlCanvasInterop.GLInfo? _jsGlInfo = null!;
2424
private InputHelperInterop _inputHelper = null!;
25+
private InputHelperInterop _canvasHelper = null!;
2526
private ElementReference _htmlCanvas;
2627
private ElementReference _inputElement;
2728
private double _dpi;
@@ -254,9 +255,11 @@ protected override void OnAfterRender(bool firstRender)
254255
Threading.Dispatcher.UIThread.Post(async () =>
255256
{
256257
_inputHelper = await InputHelperInterop.ImportAsync(Js, _inputElement);
258+
_canvasHelper = await InputHelperInterop.ImportAsync(Js, _htmlCanvas);
257259

258260
_inputHelper.Hide();
259-
_inputHelper.SetCursor("default");
261+
_canvasHelper.SetCursor("default");
262+
_topLevelImpl.SetCssCursor = _canvasHelper.SetCursor;
260263

261264
Console.WriteLine("starting html canvas setup");
262265
_interop = await SKHtmlCanvasInterop.ImportAsync(Js, _htmlCanvas, OnRenderFrame);

src/Web/Avalonia.Web.Blazor/Cursor.cs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
using Avalonia.Input;
2+
using Avalonia.Platform;
3+
4+
namespace Avalonia.Web.Blazor
5+
{
6+
public class CssCursor : ICursorImpl
7+
{
8+
public const string Default = "default";
9+
public string? Value { get; set; }
10+
11+
public CssCursor(StandardCursorType type)
12+
{
13+
Value = ToKeyword(type);
14+
}
15+
16+
/// <summary>
17+
/// Create a cursor from base64 image
18+
/// </summary>
19+
public CssCursor(string base64, string format, PixelPoint hotspot, StandardCursorType fallback)
20+
{
21+
Value = $"url(\"data:image/{format};base64,{base64}\") {hotspot.X} {hotspot.Y}, {ToKeyword(fallback)}";
22+
}
23+
24+
/// <summary>
25+
/// Create a cursor from url to *.cur file.
26+
/// </summary>
27+
public CssCursor(string url, StandardCursorType fallback)
28+
{
29+
Value = $"url('{url}'), {ToKeyword(fallback)}";
30+
}
31+
32+
/// <summary>
33+
/// Create a cursor from png/svg and hotspot position
34+
/// </summary>
35+
public CssCursor(string url, PixelPoint hotSpot, StandardCursorType fallback)
36+
{
37+
Value = $"url('{url}') {hotSpot.X} {hotSpot.Y}, {ToKeyword(fallback)}";
38+
}
39+
40+
private static string ToKeyword(StandardCursorType type) => type switch
41+
{
42+
StandardCursorType.Hand => "pointer",
43+
StandardCursorType.Cross => "crosshair",
44+
StandardCursorType.Help => "help",
45+
StandardCursorType.Ibeam => "text",
46+
StandardCursorType.No => "not-allowed",
47+
StandardCursorType.None => "none",
48+
StandardCursorType.Wait => "progress",
49+
StandardCursorType.AppStarting => "wait",
50+
51+
StandardCursorType.DragMove => "move",
52+
StandardCursorType.DragCopy => "copy",
53+
StandardCursorType.DragLink => "alias",
54+
55+
StandardCursorType.UpArrow => "default",/*not found matching one*/
56+
StandardCursorType.SizeWestEast => "ew-resize",
57+
StandardCursorType.SizeNorthSouth => "ns-resize",
58+
StandardCursorType.SizeAll => "move",
59+
60+
StandardCursorType.TopSide => "n-resize",
61+
StandardCursorType.BottomSide => "s-resize",
62+
StandardCursorType.LeftSide => "w-resize",
63+
StandardCursorType.RightSide => "e-resize",
64+
StandardCursorType.TopLeftCorner => "nw-resize",
65+
StandardCursorType.TopRightCorner => "ne-resize",
66+
StandardCursorType.BottomLeftCorner => "sw-resize",
67+
StandardCursorType.BottomRightCorner => "se-resize",
68+
69+
_ => Default,
70+
};
71+
72+
public void Dispose() {}
73+
}
74+
75+
internal class CssCursorFactory : ICursorFactory
76+
{
77+
public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot)
78+
{
79+
using var imageStream = new MemoryStream();
80+
cursor.Save(imageStream);
81+
82+
//not memory optimized because CryptoStream with ToBase64Transform is not supported in the browser.
83+
var base64String = Convert.ToBase64String(imageStream.ToArray());
84+
return new CssCursor(base64String, "png", hotSpot, StandardCursorType.Arrow);
85+
}
86+
87+
ICursorImpl ICursorFactory.GetCursor(StandardCursorType cursorType)
88+
{
89+
return new CssCursor(cursorType);
90+
}
91+
}
92+
}
93+

src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ internal class RazorViewTopLevelImpl : ITopLevelImplWithTextInputMethod
2121
private readonly Stopwatch _sw = Stopwatch.StartNew();
2222
private readonly ITextInputMethodImpl _textInputMethod;
2323
private readonly TouchDevice _touchDevice;
24+
private string _currentCursor = CssCursor.Default;
2425

2526
public RazorViewTopLevelImpl(ITextInputMethodImpl textInputMethod)
2627
{
@@ -126,8 +127,16 @@ public void SetInputRoot(IInputRoot inputRoot)
126127

127128
public void SetCursor(ICursorImpl cursor)
128129
{
129-
// nop
130-
130+
var cur = cursor as CssCursor;
131+
var val = CssCursor.Default;
132+
if (cur != null && cur.Value != null)
133+
{
134+
val = cur.Value;
135+
}
136+
if (_currentCursor != val)
137+
{
138+
SetCssCursor?.Invoke(val);
139+
}
131140
}
132141

133142
public IPopupImpl? CreatePopup()
@@ -146,6 +155,7 @@ public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
146155

147156
public IEnumerable<object> Surfaces => new object[] { _currentSurface! };
148157

158+
public Action<string>? SetCssCursor { get; set; }
149159
public Action<RawInputEventArgs>? Input { get; set; }
150160
public Action<Rect>? Paint { get; set; }
151161
public Action<Size, PlatformResizeReason>? Resized { get; set; }

src/Web/Avalonia.Web.Blazor/WinStubs.cs

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,6 @@ internal class ClipboardStub : IClipboard
2323
public Task<object> GetDataAsync(string format) => Task.FromResult<object>(new ());
2424
}
2525

26-
internal class CursorStub : ICursorImpl
27-
{
28-
public void Dispose()
29-
{
30-
31-
}
32-
}
33-
34-
internal class CursorFactoryStub : ICursorFactory
35-
{
36-
public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot)
37-
{
38-
return new CursorStub();
39-
}
40-
41-
ICursorImpl ICursorFactory.GetCursor(StandardCursorType cursorType)
42-
{
43-
return new CursorStub();
44-
}
45-
}
46-
4726
internal class IconLoaderStub : IPlatformIconLoader
4827
{
4928
private class IconStub : IWindowIconImpl

src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public static void Register()
3535
s_keyboard = new KeyboardDevice();
3636
AvaloniaLocator.CurrentMutable
3737
.Bind<IClipboard>().ToSingleton<ClipboardStub>()
38-
.Bind<ICursorFactory>().ToSingleton<CursorFactoryStub>()
38+
.Bind<ICursorFactory>().ToSingleton<CssCursorFactory>()
3939
.Bind<IKeyboardDevice>().ToConstant(s_keyboard)
4040
.Bind<IPlatformSettings>().ToConstant(instance)
4141
.Bind<IPlatformThreadingInterface>().ToConstant(instance)

0 commit comments

Comments
 (0)