Selaa lähdekoodia

Merge pull request #7295 from Mikolaytis/WasmCursors

[WASM] Implement Cursors API
Max Katz 3 vuotta sitten
vanhempi
sitoutus
ba9c0b3cfd

+ 4 - 1
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);

+ 93 - 0
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);
+        }
+
+        /// <summary>
+        /// Create a cursor from base64 image
+        /// </summary>
+        public CssCursor(string base64, string format, PixelPoint hotspot, StandardCursorType fallback)
+        {
+            Value = $"url(\"data:image/{format};base64,{base64}\") {hotspot.X} {hotspot.Y}, {ToKeyword(fallback)}";
+        }
+        
+        /// <summary>
+        /// Create a cursor from url to *.cur file.
+        /// </summary>
+        public CssCursor(string url, StandardCursorType fallback)
+        {
+            Value = $"url('{url}'), {ToKeyword(fallback)}";
+        }
+        
+        /// <summary>
+        /// Create a cursor from png/svg and hotspot position
+        /// </summary>
+        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);
+        }
+    }
+}
+

+ 12 - 2
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<object> Surfaces => new object[] { _currentSurface! };
 
+        public Action<string>? SetCssCursor { get; set; }
         public Action<RawInputEventArgs>? Input { get; set; }
         public Action<Rect>? Paint { get; set; }
         public Action<Size, PlatformResizeReason>? Resized { get; set; }

+ 0 - 21
src/Web/Avalonia.Web.Blazor/WinStubs.cs

@@ -23,27 +23,6 @@ namespace Avalonia.Web.Blazor
         public Task<object> GetDataAsync(string format) => Task.FromResult<object>(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

+ 1 - 1
src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs

@@ -35,7 +35,7 @@ namespace Avalonia.Web.Blazor
             s_keyboard = new KeyboardDevice();
             AvaloniaLocator.CurrentMutable
                 .Bind<IClipboard>().ToSingleton<ClipboardStub>()
-                .Bind<ICursorFactory>().ToSingleton<CursorFactoryStub>()
+                .Bind<ICursorFactory>().ToSingleton<CssCursorFactory>()
                 .Bind<IKeyboardDevice>().ToConstant(s_keyboard)
                 .Bind<IPlatformSettings>().ToConstant(instance)
                 .Bind<IPlatformThreadingInterface>().ToConstant(instance)