From fda517d13e7371c4577974dbc4ea3d6bc5d78201 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 17 Oct 2020 14:24:52 +0200 Subject: [PATCH 01/16] Make filename match contained class. --- src/Avalonia.Input/{Cursors.cs => Cursor.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Avalonia.Input/{Cursors.cs => Cursor.cs} (100%) diff --git a/src/Avalonia.Input/Cursors.cs b/src/Avalonia.Input/Cursor.cs similarity index 100% rename from src/Avalonia.Input/Cursors.cs rename to src/Avalonia.Input/Cursor.cs From 3124152913cf91658836e7d9b4faef246a573178 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 17 Oct 2020 16:37:27 +0200 Subject: [PATCH 02/16] Enable NRT for cursor API. --- src/Avalonia.Input/Cursor.cs | 2 ++ src/Avalonia.Input/Platform/IStandardCursorFactory.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/Avalonia.Input/Cursor.cs b/src/Avalonia.Input/Cursor.cs index 920b598eac..0c858aacdd 100644 --- a/src/Avalonia.Input/Cursor.cs +++ b/src/Avalonia.Input/Cursor.cs @@ -1,6 +1,8 @@ using System; using Avalonia.Platform; +#nullable enable + namespace Avalonia.Input { /* diff --git a/src/Avalonia.Input/Platform/IStandardCursorFactory.cs b/src/Avalonia.Input/Platform/IStandardCursorFactory.cs index 51845cf03e..ede19e91b4 100644 --- a/src/Avalonia.Input/Platform/IStandardCursorFactory.cs +++ b/src/Avalonia.Input/Platform/IStandardCursorFactory.cs @@ -1,5 +1,7 @@ using Avalonia.Input; +#nullable enable + namespace Avalonia.Platform { public interface IStandardCursorFactory From 03a18f56bbc6c70542db99e0056d94b360a78d91 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 17 Oct 2020 16:38:24 +0200 Subject: [PATCH 03/16] Initial support for custom cursors on Win32. --- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 1 + .../HeadlessPlatformStubs.cs | 6 +- src/Avalonia.Input/Cursor.cs | 20 ++--- .../Platform/IStandardCursorFactory.cs | 1 + src/Avalonia.Native/Cursor.cs | 5 ++ src/Avalonia.X11/X11CursorFactory.cs | 5 ++ src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs | 5 ++ src/Windows/Avalonia.Win32/CursorFactory.cs | 80 +++++++++++++++++++ .../Interop/UnmanagedMethods.cs | 13 +++ .../CursorFactoryMock.cs | 5 ++ 10 files changed, 130 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index f377b1bcd1..a400102877 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -195,6 +195,7 @@ namespace Avalonia.DesignerSupport.Remote class CursorFactoryStub : IStandardCursorFactory { public IPlatformHandle GetCursor(StandardCursorType cursorType) => new PlatformHandle(IntPtr.Zero, "STUB"); + public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new PlatformHandle(IntPtr.Zero, "STUB"); } class IconLoaderStub : IPlatformIconLoader diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index 4c0e2982f4..43e6731eee 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -54,11 +54,15 @@ namespace Avalonia.Headless class HeadlessCursorFactoryStub : IStandardCursorFactory { - public IPlatformHandle GetCursor(StandardCursorType cursorType) { return new PlatformHandle(new IntPtr((int)cursorType), "STUB"); } + + public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + { + return new PlatformHandle(IntPtr.Zero, "STUB"); + } } class HeadlessPlatformSettingsStub : IPlatformSettings diff --git a/src/Avalonia.Input/Cursor.cs b/src/Avalonia.Input/Cursor.cs index 0c858aacdd..21c2b1a326 100644 --- a/src/Avalonia.Input/Cursor.cs +++ b/src/Avalonia.Input/Cursor.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Media.Imaging; using Avalonia.Platform; #nullable enable @@ -58,7 +59,12 @@ namespace Avalonia.Input } public Cursor(StandardCursorType cursorType) - : this(GetCursor(cursorType)) + : this(GetCursorFactory().GetCursor(cursorType)) + { + } + + public Cursor(IBitmap cursor, PixelPoint hotSpot) + : this(GetCursorFactory().CreateCursor(cursor.PlatformImpl.Item, hotSpot)) { } @@ -71,16 +77,10 @@ namespace Avalonia.Input throw new ArgumentException($"Unrecognized cursor type '{s}'."); } - private static IPlatformHandle GetCursor(StandardCursorType type) + private static IStandardCursorFactory GetCursorFactory() { - var platform = AvaloniaLocator.Current.GetService(); - - if (platform == null) - { - throw new Exception("Could not create Cursor: IStandardCursorFactory not registered."); - } - - return platform.GetCursor(type); + return AvaloniaLocator.Current.GetService() ?? + throw new Exception("Could not create Cursor: ICursorFactory not registered."); } } } diff --git a/src/Avalonia.Input/Platform/IStandardCursorFactory.cs b/src/Avalonia.Input/Platform/IStandardCursorFactory.cs index ede19e91b4..12c3ec7b74 100644 --- a/src/Avalonia.Input/Platform/IStandardCursorFactory.cs +++ b/src/Avalonia.Input/Platform/IStandardCursorFactory.cs @@ -7,5 +7,6 @@ namespace Avalonia.Platform public interface IStandardCursorFactory { IPlatformHandle GetCursor(StandardCursorType cursorType); + IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot); } } diff --git a/src/Avalonia.Native/Cursor.cs b/src/Avalonia.Native/Cursor.cs index 3c65367c12..fffff51bce 100644 --- a/src/Avalonia.Native/Cursor.cs +++ b/src/Avalonia.Native/Cursor.cs @@ -38,5 +38,10 @@ namespace Avalonia.Native var cursor = _native.GetCursor((AvnStandardCursorType)cursorType); return new AvaloniaNativeCursor( cursor ); } + + public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + { + throw new NotImplementedException(); + } } } diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs index 1c995475ae..a82fe76cd5 100644 --- a/src/Avalonia.X11/X11CursorFactory.cs +++ b/src/Avalonia.X11/X11CursorFactory.cs @@ -67,6 +67,11 @@ namespace Avalonia.X11 return new PlatformHandle(handle, "XCURSOR"); } + public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + { + throw new NotImplementedException(); + } + private static IntPtr GetNullCursor(IntPtr display) { XColor color = new XColor(); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs b/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs index 7a257da0dd..8763f18cc0 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs @@ -10,6 +10,11 @@ namespace Avalonia.LinuxFramebuffer { return new PlatformHandle(IntPtr.Zero, null); } + + public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + { + return new PlatformHandle(IntPtr.Zero, null); + } } internal class PlatformSettings : IPlatformSettings { diff --git a/src/Windows/Avalonia.Win32/CursorFactory.cs b/src/Windows/Avalonia.Win32/CursorFactory.cs index 8c40eaa7b8..844f230a2e 100644 --- a/src/Windows/Avalonia.Win32/CursorFactory.cs +++ b/src/Windows/Avalonia.Win32/CursorFactory.cs @@ -1,8 +1,15 @@ using System; using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Runtime.InteropServices; using Avalonia.Input; +using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Win32.Interop; +using SdBitmap = System.Drawing.Bitmap; +using SdPixelFormat = System.Drawing.Imaging.PixelFormat; namespace Avalonia.Win32 { @@ -87,5 +94,78 @@ namespace Avalonia.Win32 return rv; } + + public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + { + using var source = LoadSystemDrawingBitmap(cursor); + using var mask = AlphaToMask(source); + + var info = new UnmanagedMethods.ICONINFO + { + IsIcon = false, + xHotspot = hotSpot.X, + yHotspot = hotSpot.Y, + MaskBitmap = mask.GetHbitmap(), + ColorBitmap = source.GetHbitmap(), + }; + + return new PlatformHandle( + UnmanagedMethods.CreateIconIndirect(ref info), + PlatformConstants.CursorHandleType); + } + + private SdBitmap LoadSystemDrawingBitmap(IBitmapImpl bitmap) + { + using var memoryStream = new MemoryStream(); + bitmap.Save(memoryStream); + return new SdBitmap(memoryStream); + } + + private unsafe SdBitmap AlphaToMask(SdBitmap source) + { + var dest = new SdBitmap(source.Width, source.Height, SdPixelFormat.Format1bppIndexed); + + if (source.PixelFormat != SdPixelFormat.Format32bppArgb && + source.PixelFormat != SdPixelFormat.Format32bppPArgb) + { + return dest; + } + + var sourceData = source.LockBits( + new Rectangle(default, source.Size), + ImageLockMode.ReadOnly, + SdPixelFormat.Format32bppArgb); + var destData = dest.LockBits( + new Rectangle(default, source.Size), + ImageLockMode.ReadOnly, + SdPixelFormat.Format1bppIndexed); + + try + { + var pSource = (byte*)sourceData.Scan0.ToPointer(); + var pDest = (byte*)destData.Scan0.ToPointer(); + + for (var y = 0; y < dest.Height; ++y) + { + for (var x = 0; x < dest.Width; ++x) + { + if (pSource[x * 4] == 0) + { + pDest[x / 8] |= (byte)(1 << (x % 8)); + } + } + + pSource += sourceData.Stride; + pDest += destData.Stride; + } + + return dest; + } + finally + { + source.UnlockBits(sourceData); + dest.UnlockBits(destData); + } + } } } diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 5feb6c9e46..c18a5c07c5 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1031,6 +1031,9 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern IntPtr LoadCursor(IntPtr hInstance, IntPtr lpCursorName); + [DllImport("user32.dll")] + public static extern IntPtr CreateIconIndirect([In] ref ICONINFO iconInfo); + [DllImport("user32.dll")] public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg); @@ -1743,6 +1746,16 @@ namespace Avalonia.Win32.Interop public int CyContact; } + [StructLayout(LayoutKind.Sequential)] + public struct ICONINFO + { + public bool IsIcon; + public int xHotspot; + public int yHotspot; + public IntPtr MaskBitmap; + public IntPtr ColorBitmap; + }; + [Flags] public enum TouchInputFlags { diff --git a/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs b/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs index da02cccdc5..66c6033fb4 100644 --- a/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs +++ b/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs @@ -10,5 +10,10 @@ namespace Avalonia.Controls.UnitTests { return new PlatformHandle(IntPtr.Zero, cursorType.ToString()); } + + public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + { + return new PlatformHandle(IntPtr.Zero, "Custom"); + } } } From 193cdb3324d82a3dc7207a5bf980fa45bd9b639e Mon Sep 17 00:00:00 2001 From: grokys Date: Mon, 19 Oct 2020 13:44:54 +0200 Subject: [PATCH 04/16] Implement custom cursors on X11. --- src/Avalonia.X11/X11CursorFactory.cs | 59 +++++++++++++++++++++++++++- src/Avalonia.X11/X11Structs.cs | 2 +- src/Avalonia.X11/XLib.cs | 7 ++++ 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs index a82fe76cd5..176878ed5f 100644 --- a/src/Avalonia.X11/X11CursorFactory.cs +++ b/src/Avalonia.X11/X11CursorFactory.cs @@ -1,8 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; +using Avalonia.Controls.Platform.Surfaces; using Avalonia.Input; using Avalonia.Platform; +using Avalonia.Utilities; + +#nullable enable namespace Avalonia.X11 { @@ -67,9 +72,9 @@ namespace Avalonia.X11 return new PlatformHandle(handle, "XCURSOR"); } - public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + public unsafe IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) { - throw new NotImplementedException(); + return new XImageCursor(_display, cursor, hotSpot); } private static IntPtr GetNullCursor(IntPtr display) @@ -79,5 +84,55 @@ namespace Avalonia.X11 IntPtr pixmap = XLib.XCreateBitmapFromData(display, window, NullCursorData, 1, 1); return XLib.XCreatePixmapCursor(display, pixmap, pixmap, ref color, ref color, 0, 0); } + + private unsafe class XImageCursor : IFramebufferPlatformSurface, IPlatformHandle, IDisposable + { + private readonly PixelSize _pixelSize; + private readonly IUnmanagedBlob _blob; + + public XImageCursor(IntPtr display, IBitmapImpl bitmap, PixelPoint hotSpot) + { + var size = Marshal.SizeOf() + + (bitmap.PixelSize.Width * bitmap.PixelSize.Height * 4); + + _pixelSize = bitmap.PixelSize; + _blob = AvaloniaLocator.Current.GetService().AllocBlob(size); + + var image = (XcursorImage*)_blob.Address; + image->version = 1; + image->size = Marshal.SizeOf(); + image->width = bitmap.PixelSize.Width; + image->height = bitmap.PixelSize.Height; + image->xhot = hotSpot.X; + image->yhot = hotSpot.Y; + image->pixels = (IntPtr)(image + 1); + + using (var renderTarget = AvaloniaLocator.Current.GetService().CreateRenderTarget(new[] { this })) + using (var ctx = renderTarget.CreateDrawingContext(null)) + { + var r = new Rect(_pixelSize.ToSize(1)); + ctx.DrawBitmap(RefCountable.CreateUnownedNotClonable(bitmap), 1, r, r); + } + + Handle = XLib.XcursorImageLoadCursor(display, _blob.Address); + } + + public IntPtr Handle { get; } + public string HandleDescriptor => "XCURSOR"; + + public void Dispose() + { + XLib.XcursorImageDestroy(Handle); + _blob.Dispose(); + } + + public ILockedFramebuffer Lock() + { + return new LockedFramebuffer( + _blob.Address + Marshal.SizeOf(), + _pixelSize, _pixelSize.Width * 4, + new Vector(96, 96), PixelFormat.Bgra8888, null); + } + } } } diff --git a/src/Avalonia.X11/X11Structs.cs b/src/Avalonia.X11/X11Structs.cs index 0ae5c16aef..7ee764e8a3 100644 --- a/src/Avalonia.X11/X11Structs.cs +++ b/src/Avalonia.X11/X11Structs.cs @@ -1684,7 +1684,7 @@ namespace Avalonia.X11 { [StructLayout (LayoutKind.Sequential)] internal struct XcursorImage { - private int version; + public int version; public int size; /* nominal size for matching */ public int width; /* actual width */ public int height; /* actual height */ diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs index 85aa4862b7..f5da691caa 100644 --- a/src/Avalonia.X11/XLib.cs +++ b/src/Avalonia.X11/XLib.cs @@ -19,6 +19,7 @@ namespace Avalonia.X11 const string libX11Randr = "libXrandr.so.2"; const string libX11Ext = "libXext.so.6"; const string libXInput = "libXi.so.6"; + const string libXCursor = "libXcursor.so.1"; [DllImport(libX11)] public static extern IntPtr XOpenDisplay(IntPtr display); @@ -512,6 +513,12 @@ namespace Avalonia.X11 [DllImport(libXInput)] public static extern void XIFreeDeviceInfo(XIDeviceInfo* info); + [DllImport(libXCursor)] + public static extern IntPtr XcursorImageLoadCursor(IntPtr display, IntPtr image); + + [DllImport(libXCursor)] + public static extern IntPtr XcursorImageDestroy(IntPtr image); + public static void XISetMask(ref int mask, XiEventType ev) { mask |= (1 << (int)ev); From a832d639155ea05b84d35fa17a48c32b1e0366b1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 20 Oct 2020 13:46:47 +0200 Subject: [PATCH 05/16] IStandardCursorFactory -> ICursorFactory. --- .../Remote/PreviewerWindowingPlatform.cs | 2 +- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 2 +- src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs | 2 +- src/Avalonia.Headless/HeadlessPlatformStubs.cs | 2 +- src/Avalonia.Input/Cursor.cs | 4 ++-- .../{IStandardCursorFactory.cs => ICursorFactory.cs} | 2 +- src/Avalonia.Native/AvaloniaNativePlatform.cs | 2 +- src/Avalonia.Native/Cursor.cs | 2 +- src/Avalonia.Native/WindowImplBase.cs | 4 ++-- src/Avalonia.X11/X11CursorFactory.cs | 2 +- src/Avalonia.X11/X11Platform.cs | 2 +- .../Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs | 2 +- src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs | 2 +- src/Windows/Avalonia.Win32/CursorFactory.cs | 2 +- src/Windows/Avalonia.Win32/Win32Platform.cs | 2 +- .../CalendarDatePickerTests.cs | 2 +- tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs | 2 +- tests/Avalonia.Controls.UnitTests/DatePickerTests.cs | 2 +- tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs | 4 ++-- tests/Avalonia.Controls.UnitTests/TextBoxTests.cs | 4 ++-- .../TextBoxTests_DataValidation.cs | 2 +- tests/Avalonia.Controls.UnitTests/TimePickerTests.cs | 2 +- tests/Avalonia.UnitTests/TestServices.cs | 8 ++++---- tests/Avalonia.UnitTests/UnitTestApplication.cs | 2 +- 24 files changed, 31 insertions(+), 31 deletions(-) rename src/Avalonia.Input/Platform/{IStandardCursorFactory.cs => ICursorFactory.cs} (83%) diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs index fe4c580bbb..67b832318a 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs @@ -47,7 +47,7 @@ namespace Avalonia.DesignerSupport.Remote var threading = new InternalPlatformThreadingInterface(); AvaloniaLocator.CurrentMutable .Bind().ToSingleton() - .Bind().ToSingleton() + .Bind().ToSingleton() .Bind().ToConstant(Keyboard) .Bind().ToConstant(instance) .Bind().ToConstant(threading) diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index a400102877..df1e5d6e98 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -192,7 +192,7 @@ namespace Avalonia.DesignerSupport.Remote public Task GetDataAsync(string format) => Task.FromResult((object)null); } - class CursorFactoryStub : IStandardCursorFactory + class CursorFactoryStub : ICursorFactory { public IPlatformHandle GetCursor(StandardCursorType cursorType) => new PlatformHandle(IntPtr.Zero, "STUB"); public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new PlatformHandle(IntPtr.Zero, "STUB"); diff --git a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs b/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs index 1f750a0309..fca2a1336f 100644 --- a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs +++ b/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs @@ -58,7 +58,7 @@ namespace Avalonia.Headless AvaloniaLocator.CurrentMutable .Bind().ToConstant(new HeadlessPlatformThreadingInterface()) .Bind().ToSingleton() - .Bind().ToSingleton() + .Bind().ToSingleton() .Bind().ToConstant(new HeadlessPlatformSettingsStub()) .Bind().ToSingleton() .Bind().ToSingleton() diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index 43e6731eee..efad5133b9 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -52,7 +52,7 @@ namespace Avalonia.Headless } } - class HeadlessCursorFactoryStub : IStandardCursorFactory + class HeadlessCursorFactoryStub : ICursorFactory { public IPlatformHandle GetCursor(StandardCursorType cursorType) { diff --git a/src/Avalonia.Input/Cursor.cs b/src/Avalonia.Input/Cursor.cs index 21c2b1a326..f018041a94 100644 --- a/src/Avalonia.Input/Cursor.cs +++ b/src/Avalonia.Input/Cursor.cs @@ -77,9 +77,9 @@ namespace Avalonia.Input throw new ArgumentException($"Unrecognized cursor type '{s}'."); } - private static IStandardCursorFactory GetCursorFactory() + private static ICursorFactory GetCursorFactory() { - return AvaloniaLocator.Current.GetService() ?? + return AvaloniaLocator.Current.GetService() ?? throw new Exception("Could not create Cursor: ICursorFactory not registered."); } } diff --git a/src/Avalonia.Input/Platform/IStandardCursorFactory.cs b/src/Avalonia.Input/Platform/ICursorFactory.cs similarity index 83% rename from src/Avalonia.Input/Platform/IStandardCursorFactory.cs rename to src/Avalonia.Input/Platform/ICursorFactory.cs index 12c3ec7b74..e531015d40 100644 --- a/src/Avalonia.Input/Platform/IStandardCursorFactory.cs +++ b/src/Avalonia.Input/Platform/ICursorFactory.cs @@ -4,7 +4,7 @@ using Avalonia.Input; namespace Avalonia.Platform { - public interface IStandardCursorFactory + public interface ICursorFactory { IPlatformHandle GetCursor(StandardCursorType cursorType); IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot); diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index e8b2f065c7..c56a8404fa 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -99,7 +99,7 @@ namespace Avalonia.Native AvaloniaLocator.CurrentMutable .Bind() .ToConstant(new PlatformThreadingInterface(_factory.CreatePlatformThreadingInterface())) - .Bind().ToConstant(new CursorFactory(_factory.CreateCursorFactory())) + .Bind().ToConstant(new CursorFactory(_factory.CreateCursorFactory())) .Bind().ToSingleton() .Bind().ToConstant(KeyboardDevice) .Bind().ToConstant(this) diff --git a/src/Avalonia.Native/Cursor.cs b/src/Avalonia.Native/Cursor.cs index fffff51bce..ebee4baafe 100644 --- a/src/Avalonia.Native/Cursor.cs +++ b/src/Avalonia.Native/Cursor.cs @@ -24,7 +24,7 @@ namespace Avalonia.Native } } - class CursorFactory : IStandardCursorFactory + class CursorFactory : ICursorFactory { IAvnCursorFactory _native; diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 5d35c773d7..e4643bf675 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -53,7 +53,7 @@ namespace Avalonia.Native private bool _gpu = false; private readonly MouseDevice _mouse; private readonly IKeyboardDevice _keyboard; - private readonly IStandardCursorFactory _cursorFactory; + private readonly ICursorFactory _cursorFactory; private Size _savedLogicalSize; private Size _lastRenderedLogicalSize; private double _savedScaling; @@ -68,7 +68,7 @@ namespace Avalonia.Native _keyboard = AvaloniaLocator.Current.GetService(); _mouse = new MouseDevice(); - _cursorFactory = AvaloniaLocator.Current.GetService(); + _cursorFactory = AvaloniaLocator.Current.GetService(); } protected void Init(IAvnWindowBase window, IAvnScreens screens, IGlContext glContext) diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs index 176878ed5f..7f08407239 100644 --- a/src/Avalonia.X11/X11CursorFactory.cs +++ b/src/Avalonia.X11/X11CursorFactory.cs @@ -11,7 +11,7 @@ using Avalonia.Utilities; namespace Avalonia.X11 { - class X11CursorFactory : IStandardCursorFactory + class X11CursorFactory : ICursorFactory { private static readonly byte[] NullCursorData = new byte[] { 0 }; diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index c6db146f7b..ffd7c75608 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -52,7 +52,7 @@ namespace Avalonia.X11 .Bind().ToConstant(new RenderLoop()) .Bind().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Control)) .Bind().ToFunc(() => KeyboardDevice) - .Bind().ToConstant(new X11CursorFactory(Display)) + .Bind().ToConstant(new X11CursorFactory(Display)) .Bind().ToConstant(new X11Clipboard(this)) .Bind().ToConstant(new PlatformSettingsStub()) .Bind().ToConstant(new X11IconLoader(Info)) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index 8801f71f9a..340d10517d 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -38,7 +38,7 @@ namespace Avalonia.LinuxFramebuffer .Bind().ToConstant(Threading) .Bind().ToConstant(new DefaultRenderTimer(60)) .Bind().ToConstant(new RenderLoop()) - .Bind().ToTransient() + .Bind().ToTransient() .Bind().ToConstant(new KeyboardDevice()) .Bind().ToSingleton() .Bind().ToConstant(new RenderLoop()) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs b/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs index 8763f18cc0..def5f6b817 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs @@ -4,7 +4,7 @@ using Avalonia.Platform; namespace Avalonia.LinuxFramebuffer { - internal class CursorFactoryStub : IStandardCursorFactory + internal class CursorFactoryStub : ICursorFactory { public IPlatformHandle GetCursor(StandardCursorType cursorType) { diff --git a/src/Windows/Avalonia.Win32/CursorFactory.cs b/src/Windows/Avalonia.Win32/CursorFactory.cs index 844f230a2e..aca5f42771 100644 --- a/src/Windows/Avalonia.Win32/CursorFactory.cs +++ b/src/Windows/Avalonia.Win32/CursorFactory.cs @@ -13,7 +13,7 @@ using SdPixelFormat = System.Drawing.Imaging.PixelFormat; namespace Avalonia.Win32 { - internal class CursorFactory : IStandardCursorFactory + internal class CursorFactory : ICursorFactory { public static CursorFactory Instance { get; } = new CursorFactory(); diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 61c1a4f45e..ebc69072fc 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -90,7 +90,7 @@ namespace Avalonia.Win32 Options = options; AvaloniaLocator.CurrentMutable .Bind().ToSingleton() - .Bind().ToConstant(CursorFactory.Instance) + .Bind().ToConstant(CursorFactory.Instance) .Bind().ToConstant(WindowsKeyboardDevice.Instance) .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) diff --git a/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs b/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs index f41a3e7581..d77c7b87fa 100644 --- a/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs @@ -74,7 +74,7 @@ namespace Avalonia.Controls.UnitTests } private static TestServices Services => TestServices.MockThreadingInterface.With( - standardCursorFactory: Mock.Of()); + standardCursorFactory: Mock.Of()); private CalendarDatePicker CreateControl() { diff --git a/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs b/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs index 66c6033fb4..0c5a395ba4 100644 --- a/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs +++ b/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs @@ -4,7 +4,7 @@ using Avalonia.Platform; namespace Avalonia.Controls.UnitTests { - public class CursorFactoryMock : IStandardCursorFactory + public class CursorFactoryMock : ICursorFactory { public IPlatformHandle GetCursor(StandardCursorType cursorType) { diff --git a/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs b/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs index 7bcb120850..3105263290 100644 --- a/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DatePickerTests.cs @@ -204,7 +204,7 @@ namespace Avalonia.Controls.UnitTests private static TestServices Services => TestServices.MockThreadingInterface.With( fontManagerImpl: new MockFontManagerImpl(), - standardCursorFactory: Mock.Of(), + standardCursorFactory: Mock.Of(), textShaperImpl: new MockTextShaperImpl()); private IControlTemplate CreateTemplate() diff --git a/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs b/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs index f2b6b0db4b..72ccead783 100644 --- a/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs @@ -11,8 +11,8 @@ namespace Avalonia.Controls.UnitTests { public GridSplitterTests() { - var cursorFactoryImpl = new Mock(); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(cursorFactoryImpl.Object); + var cursorFactoryImpl = new Mock(); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(cursorFactoryImpl.Object); } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index fe25fa7346..6ac7799828 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -698,10 +698,10 @@ namespace Avalonia.Controls.UnitTests keyboardDevice: () => new KeyboardDevice(), keyboardNavigation: new KeyboardNavigationHandler(), inputManager: new InputManager(), - standardCursorFactory: Mock.Of()); + standardCursorFactory: Mock.Of()); private static TestServices Services => TestServices.MockThreadingInterface.With( - standardCursorFactory: Mock.Of()); + standardCursorFactory: Mock.Of()); private IControlTemplate CreateTemplate() { diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs index 78b6bf01ec..570b9ee4ea 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs @@ -86,7 +86,7 @@ namespace Avalonia.Controls.UnitTests } private static TestServices Services => TestServices.MockThreadingInterface.With( - standardCursorFactory: Mock.Of()); + standardCursorFactory: Mock.Of()); private IControlTemplate CreateTemplate() { diff --git a/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs b/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs index 682f0eaadb..c39a963dae 100644 --- a/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs @@ -100,7 +100,7 @@ namespace Avalonia.Controls.UnitTests private static TestServices Services => TestServices.MockThreadingInterface.With( fontManagerImpl: new MockFontManagerImpl(), - standardCursorFactory: Mock.Of(), + standardCursorFactory: Mock.Of(), textShaperImpl: new MockTextShaperImpl()); private IControlTemplate CreateTemplate() diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index 012cab23dc..8d27562146 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -23,7 +23,7 @@ namespace Avalonia.UnitTests assetLoader: new AssetLoader(), platform: new AppBuilder().RuntimePlatform, renderInterface: new MockPlatformRenderInterface(), - standardCursorFactory: Mock.Of(), + standardCursorFactory: Mock.Of(), styler: new Styler(), theme: () => CreateDefaultTheme(), threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true), @@ -70,7 +70,7 @@ namespace Avalonia.UnitTests IPlatformRenderInterface renderInterface = null, IRenderTimer renderLoop = null, IScheduler scheduler = null, - IStandardCursorFactory standardCursorFactory = null, + ICursorFactory standardCursorFactory = null, IStyler styler = null, Func theme = null, IPlatformThreadingInterface threadingInterface = null, @@ -111,7 +111,7 @@ namespace Avalonia.UnitTests public IFontManagerImpl FontManagerImpl { get; } public ITextShaperImpl TextShaperImpl { get; } public IScheduler Scheduler { get; } - public IStandardCursorFactory StandardCursorFactory { get; } + public ICursorFactory StandardCursorFactory { get; } public IStyler Styler { get; } public Func Theme { get; } public IPlatformThreadingInterface ThreadingInterface { get; } @@ -130,7 +130,7 @@ namespace Avalonia.UnitTests IPlatformRenderInterface renderInterface = null, IRenderTimer renderLoop = null, IScheduler scheduler = null, - IStandardCursorFactory standardCursorFactory = null, + ICursorFactory standardCursorFactory = null, IStyler styler = null, Func theme = null, IPlatformThreadingInterface threadingInterface = null, diff --git a/tests/Avalonia.UnitTests/UnitTestApplication.cs b/tests/Avalonia.UnitTests/UnitTestApplication.cs index e4a65f105d..6fc82088e9 100644 --- a/tests/Avalonia.UnitTests/UnitTestApplication.cs +++ b/tests/Avalonia.UnitTests/UnitTestApplication.cs @@ -62,7 +62,7 @@ namespace Avalonia.UnitTests .Bind().ToConstant(Services.TextShaperImpl) .Bind().ToConstant(Services.ThreadingInterface) .Bind().ToConstant(Services.Scheduler) - .Bind().ToConstant(Services.StandardCursorFactory) + .Bind().ToConstant(Services.StandardCursorFactory) .Bind().ToConstant(Services.Styler) .Bind().ToConstant(Services.WindowingPlatform) .Bind().ToSingleton(); From ca408e55b5091449158cfae7536960464389f6fd Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 20 Oct 2020 14:32:28 +0200 Subject: [PATCH 06/16] Added ICursorImpl. --- .../Offscreen/OffscreenTopLevelImpl.cs | 2 +- .../Platform/ITopLevelImpl.cs | 2 +- src/Avalonia.Controls/TopLevel.cs | 2 +- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 11 ++-- .../HeadlessPlatformStubs.cs | 10 ++-- src/Avalonia.Headless/HeadlessWindowImpl.cs | 2 +- src/Avalonia.Input/Cursor.cs | 6 +-- src/Avalonia.Input/Platform/ICursorFactory.cs | 4 +- src/Avalonia.Input/Platform/ICursorImpl.cs | 14 +++++ src/Avalonia.Native/Cursor.cs | 6 +-- src/Avalonia.Native/WindowImplBase.cs | 2 +- src/Avalonia.X11/X11CursorFactory.cs | 19 ++++--- src/Avalonia.X11/X11Window.cs | 8 ++- .../FramebufferToplevelImpl.cs | 2 +- src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs | 10 ++-- .../Wpf/WpfTopLevelImpl.cs | 6 +-- src/Windows/Avalonia.Win32/CursorFactory.cs | 52 ++++++++++++------- .../Interop/UnmanagedMethods.cs | 3 ++ src/Windows/Avalonia.Win32/WindowImpl.cs | 15 ++++-- .../CursorFactoryMock.cs | 16 ++++-- 20 files changed, 121 insertions(+), 71 deletions(-) create mode 100644 src/Avalonia.Input/Platform/ICursorImpl.cs diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 522103c7bd..ca0e9d48b8 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -61,7 +61,7 @@ namespace Avalonia.Controls.Embedding.Offscreen public virtual PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, 1); - public virtual void SetCursor(IPlatformHandle cursor) + public virtual void SetCursor(ICursorImpl cursor) { } diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 7514f214aa..09f38042a1 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -98,7 +98,7 @@ namespace Avalonia.Platform /// Sets the cursor associated with the toplevel. /// /// The cursor. Use null for default cursor - void SetCursor(IPlatformHandle cursor); + void SetCursor(ICursorImpl cursor); /// /// Gets or sets a method called when the underlying implementation is destroyed. diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 3d24f60463..ffccdeb3e2 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -162,7 +162,7 @@ namespace Avalonia.Controls this.GetObservable(PointerOverElementProperty) .Select( x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty()) - .Switch().Subscribe(cursor => PlatformImpl?.SetCursor(cursor?.PlatformCursor)); + .Switch().Subscribe(cursor => PlatformImpl?.SetCursor(cursor?.PlatformImpl)); if (((IStyleHost)this).StylingParent is IResourceHost applicationResources) { diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index df1e5d6e98..751c5c08f2 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -73,7 +73,7 @@ namespace Avalonia.DesignerSupport.Remote public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1); - public void SetCursor(IPlatformHandle cursor) + public void SetCursor(ICursorImpl cursor) { } @@ -194,8 +194,13 @@ namespace Avalonia.DesignerSupport.Remote class CursorFactoryStub : ICursorFactory { - public IPlatformHandle GetCursor(StandardCursorType cursorType) => new PlatformHandle(IntPtr.Zero, "STUB"); - public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new PlatformHandle(IntPtr.Zero, "STUB"); + public ICursorImpl GetCursor(StandardCursorType cursorType) => new CursorStub(); + public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new CursorStub(); + + private class CursorStub : ICursorImpl + { + public void Dispose() { } + } } class IconLoaderStub : IPlatformIconLoader diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index efad5133b9..ce4c31e27e 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -54,14 +54,12 @@ namespace Avalonia.Headless class HeadlessCursorFactoryStub : ICursorFactory { - public IPlatformHandle GetCursor(StandardCursorType cursorType) - { - return new PlatformHandle(new IntPtr((int)cursorType), "STUB"); - } + public ICursorImpl GetCursor(StandardCursorType cursorType) => new CursorStub(); + public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new CursorStub(); - public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + private class CursorStub : ICursorImpl { - return new PlatformHandle(IntPtr.Zero, "STUB"); + public void Dispose() { } } } diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index 8f4fa5e304..2c52438743 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -67,7 +67,7 @@ namespace Avalonia.Headless public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, RenderScaling); - public void SetCursor(IPlatformHandle cursor) + public void SetCursor(ICursorImpl cursor) { } diff --git a/src/Avalonia.Input/Cursor.cs b/src/Avalonia.Input/Cursor.cs index f018041a94..052ef5e9a0 100644 --- a/src/Avalonia.Input/Cursor.cs +++ b/src/Avalonia.Input/Cursor.cs @@ -53,9 +53,9 @@ namespace Avalonia.Input { public static readonly Cursor Default = new Cursor(StandardCursorType.Arrow); - internal Cursor(IPlatformHandle platformCursor) + internal Cursor(ICursorImpl platformImpl) { - PlatformCursor = platformCursor; + PlatformImpl = platformImpl; } public Cursor(StandardCursorType cursorType) @@ -68,7 +68,7 @@ namespace Avalonia.Input { } - public IPlatformHandle PlatformCursor { get; } + public ICursorImpl PlatformImpl { get; } public static Cursor Parse(string s) { diff --git a/src/Avalonia.Input/Platform/ICursorFactory.cs b/src/Avalonia.Input/Platform/ICursorFactory.cs index e531015d40..fff1f92d53 100644 --- a/src/Avalonia.Input/Platform/ICursorFactory.cs +++ b/src/Avalonia.Input/Platform/ICursorFactory.cs @@ -6,7 +6,7 @@ namespace Avalonia.Platform { public interface ICursorFactory { - IPlatformHandle GetCursor(StandardCursorType cursorType); - IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot); + ICursorImpl GetCursor(StandardCursorType cursorType); + ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot); } } diff --git a/src/Avalonia.Input/Platform/ICursorImpl.cs b/src/Avalonia.Input/Platform/ICursorImpl.cs new file mode 100644 index 0000000000..14235869f7 --- /dev/null +++ b/src/Avalonia.Input/Platform/ICursorImpl.cs @@ -0,0 +1,14 @@ +using System; +using Avalonia.Input; + +#nullable enable + +namespace Avalonia.Platform +{ + /// + /// Represents a platform implementation of a . + /// + public interface ICursorImpl : IDisposable + { + } +} diff --git a/src/Avalonia.Native/Cursor.cs b/src/Avalonia.Native/Cursor.cs index ebee4baafe..2cb085bc7e 100644 --- a/src/Avalonia.Native/Cursor.cs +++ b/src/Avalonia.Native/Cursor.cs @@ -5,7 +5,7 @@ using Avalonia.Native.Interop; namespace Avalonia.Native { - class AvaloniaNativeCursor : IPlatformHandle, IDisposable + class AvaloniaNativeCursor : ICursorImpl, IDisposable { public IAvnCursor Cursor { get; private set; } public IntPtr Handle => IntPtr.Zero; @@ -33,13 +33,13 @@ namespace Avalonia.Native _native = native; } - public IPlatformHandle GetCursor(StandardCursorType cursorType) + public ICursorImpl GetCursor(StandardCursorType cursorType) { var cursor = _native.GetCursor((AvnStandardCursorType)cursorType); return new AvaloniaNativeCursor( cursor ); } - public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) { throw new NotImplementedException(); } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index e4643bf675..ad2b7411eb 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -389,7 +389,7 @@ namespace Avalonia.Native public Action Deactivated { get; set; } public Action Activated { get; set; } - public void SetCursor(IPlatformHandle cursor) + public void SetCursor(ICursorImpl cursor) { if (_native == null) { diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs index 7f08407239..f95d4320fe 100644 --- a/src/Avalonia.X11/X11CursorFactory.cs +++ b/src/Avalonia.X11/X11CursorFactory.cs @@ -56,7 +56,7 @@ namespace Avalonia.X11 .ToDictionary(id => id, id => XLib.XCreateFontCursor(_display, id)); } - public IPlatformHandle GetCursor(StandardCursorType cursorType) + public ICursorImpl GetCursor(StandardCursorType cursorType) { IntPtr handle; if (cursorType == StandardCursorType.None) @@ -69,10 +69,10 @@ namespace Avalonia.X11 ? _cursors[shape] : _cursors[CursorFontShape.XC_top_left_arrow]; } - return new PlatformHandle(handle, "XCURSOR"); + return new CursorImpl(handle); } - public unsafe IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + public unsafe ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) { return new XImageCursor(_display, cursor, hotSpot); } @@ -85,7 +85,7 @@ namespace Avalonia.X11 return XLib.XCreatePixmapCursor(display, pixmap, pixmap, ref color, ref color, 0, 0); } - private unsafe class XImageCursor : IFramebufferPlatformSurface, IPlatformHandle, IDisposable + private unsafe class XImageCursor : CursorImpl, IFramebufferPlatformSurface, IPlatformHandle { private readonly PixelSize _pixelSize; private readonly IUnmanagedBlob _blob; @@ -117,10 +117,9 @@ namespace Avalonia.X11 Handle = XLib.XcursorImageLoadCursor(display, _blob.Address); } - public IntPtr Handle { get; } public string HandleDescriptor => "XCURSOR"; - public void Dispose() + public override void Dispose() { XLib.XcursorImageDestroy(Handle); _blob.Dispose(); @@ -135,4 +134,12 @@ namespace Avalonia.X11 } } } + + class CursorImpl : ICursorImpl + { + public CursorImpl() { } + public CursorImpl(IntPtr handle) => Handle = handle; + public IntPtr Handle { get; protected set; } + public virtual void Dispose() { } + } } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 2cd3b973d8..32c455571f 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -878,15 +878,13 @@ namespace Avalonia.X11 UpdateSizeHints(null); } - public void SetCursor(IPlatformHandle cursor) + public void SetCursor(ICursorImpl cursor) { if (cursor == null) XDefineCursor(_x11.Display, _handle, _x11.DefaultCursor); - else + else if (cursor is CursorImpl impl) { - if (cursor.HandleDescriptor != "XCURSOR") - throw new ArgumentException("Expected XCURSOR handle type"); - XDefineCursor(_x11.Display, _handle, cursor.Handle); + XDefineCursor(_x11.Display, _handle, impl.Handle); } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 0a101eec7a..4bbb58e53e 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -57,7 +57,7 @@ namespace Avalonia.LinuxFramebuffer public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1); - public void SetCursor(IPlatformHandle cursor) + public void SetCursor(ICursorImpl cursor) { } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs b/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs index def5f6b817..642be28c69 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs @@ -6,14 +6,12 @@ namespace Avalonia.LinuxFramebuffer { internal class CursorFactoryStub : ICursorFactory { - public IPlatformHandle GetCursor(StandardCursorType cursorType) - { - return new PlatformHandle(IntPtr.Zero, null); - } + public ICursorImpl GetCursor(StandardCursorType cursorType) => new CursorStub(); + public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new CursorStub(); - public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + private class CursorStub : ICursorImpl { - return new PlatformHandle(IntPtr.Zero, null); + public void Dispose() { } } } internal class PlatformSettings : IPlatformSettings diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 3467a33d16..3bb29f4e23 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -225,12 +225,12 @@ namespace Avalonia.Win32.Interop.Wpf protected override void OnTextInput(TextCompositionEventArgs e) => _ttl.Input?.Invoke(new RawTextInputEventArgs(_keyboard, (uint) e.Timestamp, _inputRoot, e.Text)); - void ITopLevelImpl.SetCursor(IPlatformHandle cursor) + void ITopLevelImpl.SetCursor(ICursorImpl cursor) { if (cursor == null) Cursor = Cursors.Arrow; - else if (cursor.HandleDescriptor == "HCURSOR") - Cursor = CursorShim.FromHCursor(cursor.Handle); + else if (cursor is IPlatformHandle handle) + Cursor = CursorShim.FromHCursor(handle.Handle); } Action ITopLevelImpl.Input { get; set; } //TODO diff --git a/src/Windows/Avalonia.Win32/CursorFactory.cs b/src/Windows/Avalonia.Win32/CursorFactory.cs index aca5f42771..10878ba7b5 100644 --- a/src/Windows/Avalonia.Win32/CursorFactory.cs +++ b/src/Windows/Avalonia.Win32/CursorFactory.cs @@ -3,9 +3,7 @@ using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; -using System.Runtime.InteropServices; using Avalonia.Input; -using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Win32.Interop; using SdBitmap = System.Drawing.Bitmap; @@ -36,8 +34,7 @@ namespace Avalonia.Win32 IntPtr cursor = UnmanagedMethods.LoadCursor(mh, new IntPtr(id)); if (cursor != IntPtr.Zero) { - PlatformHandle phCursor = new PlatformHandle(cursor, PlatformConstants.CursorHandleType); - Cache.Add(cursorType, phCursor); + Cache.Add(cursorType, new CursorImpl(cursor, false)); } } } @@ -77,25 +74,23 @@ namespace Avalonia.Win32 {StandardCursorType.DragLink, 32516}, }; - private static readonly Dictionary Cache = - new Dictionary(); + private static readonly Dictionary Cache = + new Dictionary(); - public IPlatformHandle GetCursor(StandardCursorType cursorType) + public ICursorImpl GetCursor(StandardCursorType cursorType) { - IPlatformHandle rv; - if (!Cache.TryGetValue(cursorType, out rv)) + if (!Cache.TryGetValue(cursorType, out var rv)) { - Cache[cursorType] = - rv = - new PlatformHandle( - UnmanagedMethods.LoadCursor(IntPtr.Zero, new IntPtr(CursorTypeMapping[cursorType])), - PlatformConstants.CursorHandleType); + rv = new CursorImpl( + UnmanagedMethods.LoadCursor(IntPtr.Zero, new IntPtr(CursorTypeMapping[cursorType])), + false); + Cache.Add(cursorType, rv); } return rv; } - public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) { using var source = LoadSystemDrawingBitmap(cursor); using var mask = AlphaToMask(source); @@ -109,9 +104,7 @@ namespace Avalonia.Win32 ColorBitmap = source.GetHbitmap(), }; - return new PlatformHandle( - UnmanagedMethods.CreateIconIndirect(ref info), - PlatformConstants.CursorHandleType); + return new CursorImpl(UnmanagedMethods.CreateIconIndirect(ref info), true); } private SdBitmap LoadSystemDrawingBitmap(IBitmapImpl bitmap) @@ -168,4 +161,27 @@ namespace Avalonia.Win32 } } } + + internal class CursorImpl : ICursorImpl, IPlatformHandle + { + private readonly bool _isCustom; + + public CursorImpl(IntPtr handle, bool isCustom) + { + Handle = handle; + _isCustom = isCustom; + } + + public IntPtr Handle { get; private set; } + public string HandleDescriptor => PlatformConstants.CursorHandleType; + + public void Dispose() + { + if (_isCustom && Handle != IntPtr.Zero) + { + UnmanagedMethods.DestroyIcon(Handle); + Handle = IntPtr.Zero; + } + } + } } diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index c18a5c07c5..3b11aecc2d 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1034,6 +1034,9 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern IntPtr CreateIconIndirect([In] ref ICONINFO iconInfo); + [DllImport("user32.dll")] + public static extern bool DestroyIcon(IntPtr hIcon); + [DllImport("user32.dll")] public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 7079a0120c..755570bff1 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -557,14 +557,19 @@ namespace Avalonia.Win32 SetWindowText(_hwnd, title); } - public void SetCursor(IPlatformHandle cursor) + public void SetCursor(ICursorImpl cursor) { - var hCursor = cursor?.Handle ?? DefaultCursor; - SetClassLong(_hwnd, ClassLongIndex.GCLP_HCURSOR, hCursor); + var impl = cursor as CursorImpl; - if (_owner.IsPointerOver) + if (cursor is null || impl is object) { - UnmanagedMethods.SetCursor(hCursor); + var hCursor = impl?.Handle ?? DefaultCursor; + SetClassLong(_hwnd, ClassLongIndex.GCLP_HCURSOR, hCursor); + + if (_owner.IsPointerOver) + { + UnmanagedMethods.SetCursor(hCursor); + } } } diff --git a/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs b/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs index 0c5a395ba4..ee4264e6b9 100644 --- a/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs +++ b/tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs @@ -1,4 +1,3 @@ -using System; using Avalonia.Input; using Avalonia.Platform; @@ -6,14 +5,21 @@ namespace Avalonia.Controls.UnitTests { public class CursorFactoryMock : ICursorFactory { - public IPlatformHandle GetCursor(StandardCursorType cursorType) + public ICursorImpl GetCursor(StandardCursorType cursorType) { - return new PlatformHandle(IntPtr.Zero, cursorType.ToString()); + return new MockCursorImpl(); } - public IPlatformHandle CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) { - return new PlatformHandle(IntPtr.Zero, "Custom"); + return new MockCursorImpl(); + } + + private class MockCursorImpl : ICursorImpl + { + public void Dispose() + { + } } } } From f0d097ed1234a9a45ab42451c52bba8afc4ac37d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 20 Oct 2020 14:34:50 +0200 Subject: [PATCH 07/16] Make Cursor disposable. --- src/Avalonia.Input/Cursor.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Input/Cursor.cs b/src/Avalonia.Input/Cursor.cs index 052ef5e9a0..2b99c51472 100644 --- a/src/Avalonia.Input/Cursor.cs +++ b/src/Avalonia.Input/Cursor.cs @@ -6,13 +6,6 @@ using Avalonia.Platform; namespace Avalonia.Input { - /* - ========================================================================================= - NOTE: Cursors are NOT disposable and are cached in platform implementation. - To support loading custom cursors some measures about that should be taken beforehand - ========================================================================================= - */ - public enum StandardCursorType { Arrow, @@ -49,7 +42,7 @@ namespace Avalonia.Input // SizeNorthEastSouthWest, } - public class Cursor + public class Cursor : IDisposable { public static readonly Cursor Default = new Cursor(StandardCursorType.Arrow); @@ -70,6 +63,8 @@ namespace Avalonia.Input public ICursorImpl PlatformImpl { get; } + public void Dispose() => PlatformImpl.Dispose(); + public static Cursor Parse(string s) { return Enum.TryParse(s, true, out var t) ? From 04f5d3a252f1c029ccc64f16684c07eb8cd6c798 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 20 Oct 2020 14:38:01 +0200 Subject: [PATCH 08/16] Don't allow premultipied alpha for the moment. --- src/Windows/Avalonia.Win32/CursorFactory.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Windows/Avalonia.Win32/CursorFactory.cs b/src/Windows/Avalonia.Win32/CursorFactory.cs index 10878ba7b5..0a0e45b03a 100644 --- a/src/Windows/Avalonia.Win32/CursorFactory.cs +++ b/src/Windows/Avalonia.Win32/CursorFactory.cs @@ -118,8 +118,13 @@ namespace Avalonia.Win32 { var dest = new SdBitmap(source.Width, source.Height, SdPixelFormat.Format1bppIndexed); - if (source.PixelFormat != SdPixelFormat.Format32bppArgb && - source.PixelFormat != SdPixelFormat.Format32bppPArgb) + if (source.PixelFormat == SdPixelFormat.Format32bppPArgb) + { + throw new NotSupportedException( + "Images with premultiplied alpha not yet supported as cursor images."); + } + + if (source.PixelFormat != SdPixelFormat.Format32bppArgb) { return dest; } From 98974af13e7e08662c32b4744e6df9f89490d97c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 21 Oct 2020 14:44:24 +0200 Subject: [PATCH 09/16] Added cursor page to control catalog. --- samples/ControlCatalog/Assets/avalonia-32.png | Bin 0 -> 1322 bytes samples/ControlCatalog/MainView.xaml | 4 ++ samples/ControlCatalog/Pages/CursorPage.xaml | 29 ++++++++++++ .../ControlCatalog/Pages/CursorPage.xaml.cs | 20 +++++++++ .../ViewModels/CursorPageViewModel.cs | 42 ++++++++++++++++++ 5 files changed, 95 insertions(+) create mode 100644 samples/ControlCatalog/Assets/avalonia-32.png create mode 100644 samples/ControlCatalog/Pages/CursorPage.xaml create mode 100644 samples/ControlCatalog/Pages/CursorPage.xaml.cs create mode 100644 samples/ControlCatalog/ViewModels/CursorPageViewModel.cs diff --git a/samples/ControlCatalog/Assets/avalonia-32.png b/samples/ControlCatalog/Assets/avalonia-32.png new file mode 100644 index 0000000000000000000000000000000000000000..7b443e7a250846dc898e6467cddeed8c55880766 GIT binary patch literal 1322 zcmV+_1=aeAP)A zL5V<&1`?F`W{`LZcsE5cBpMzF4;mv6L;;~9STsnu#G;m4u_XwFBGRT;C>`6GUgn(T zL)$4c=S({l;{UQ|_FDV=|6Y6Tb=DCggsQrL1;9dJ29OIR^qV_@TA&g*4xAK`-bkN4 z9C5m;mZ)ljMvf*`-Kwf%?}yk>)mK&3GXmmJbg1eZ{m~l{d7`S8-Uo3gep1zxNXaLv zYSjZIAHbigniI+=A>>IwIZ!Z^$o9rgT58+rX!W5AiD_}9PaZ`|X5w)By8#r7NUKeN zsv5v|z%r+mmfzx!BR9Bs_y#St?T$}Rn>32TB~y5G>114PBbTZ98d$;Vp#KA9s55xh1snb zVql0XA`$Lr7fUu3F?C*+li3I4iAdc*oHjU-H`Lr_?|aAS_Vr+?iKSLxRCGuI{vMMb zK0ZUk2-=%=h?IY zi`~EzPRh&oS7NDYn_skkCi9*z44TU>OeecAopT4SaeU`R+nUNljsgG{8T7qa0_gPk zXlwS6nU~go-h*d9ZQcYM`MsF6BA5Jy*|vF4lb7b}cb%5ZFn}oo|JrZ=!;Uw1vwh<( zW-ZO<@uv%dM9f`1WPJjlXysJL`Q~em0&)x>8Nl1o#aATvT{Op4xk1SOI5BuQ<-fC&z>p5V$uYLPByp$*GA{ zp16$P^aoYHvq!3^ID9EAdG)bc&K+_VkT53JX<7~XdN>>AE`KbD+)@+MviN388T+;# zrTOMyiM?Id&f(9>`L^T)rsa?TLqJqD7H9y1I+mWUUfx>%DV|nu*isxDAA`$q)9W*X zY)MfmgjVRHRe3B}Z@b2pAtGJC?odl~Y!ok+tVCScA~7&_dAsp-`bPYsx)%e5ejCso zYRoU1!kTxMgP~zX432}pM*;&tL~atM8JV+eCL4CFC1G@I#3CZ87oiaV_z*bhHmSh z$FX-7ktX^Yl}~^N^O5O{ABXi&)f`p*LRC8-@RyQDRdXYjs;Wt<`dq)5v#Q$iA4n}# g?NZe;Rhb%7 literal 0 HcmV?d00001 diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index bd5beafe29..34ff4b1a54 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -22,6 +22,10 @@ + + + diff --git a/samples/ControlCatalog/Pages/CursorPage.xaml b/samples/ControlCatalog/Pages/CursorPage.xaml new file mode 100644 index 0000000000..a28039ea3f --- /dev/null +++ b/samples/ControlCatalog/Pages/CursorPage.xaml @@ -0,0 +1,29 @@ + + + + Cursor + Defines a cursor (mouse pointer) + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/CursorPage.xaml.cs b/samples/ControlCatalog/Pages/CursorPage.xaml.cs new file mode 100644 index 0000000000..9e9e9ba8b9 --- /dev/null +++ b/samples/ControlCatalog/Pages/CursorPage.xaml.cs @@ -0,0 +1,20 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using ControlCatalog.ViewModels; + +namespace ControlCatalog.Pages +{ + public class CursorPage : UserControl + { + public CursorPage() + { + this.InitializeComponent(); + DataContext = new CursorPageViewModel(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs b/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs new file mode 100644 index 0000000000..a4c643dd04 --- /dev/null +++ b/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia; +using Avalonia.Input; +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using ReactiveUI; + +namespace ControlCatalog.ViewModels +{ + public class CursorPageViewModel : ReactiveObject + { + public CursorPageViewModel() + { + StandardCursors = Enum.GetValues(typeof(StandardCursorType)) + .Cast() + .Select(x => new StandardCursorModel(x)) + .ToList(); + + var loader = AvaloniaLocator.Current.GetService(); + var s = loader.Open(new Uri("avares://ControlCatalog/Assets/avalonia-32.png")); + var bitmap = new Bitmap(s); + CustomCursor = new Cursor(bitmap, new PixelPoint(16, 16)); + } + + public IEnumerable StandardCursors { get; } + public Cursor CustomCursor { get; } + + public class StandardCursorModel + { + public StandardCursorModel(StandardCursorType type) + { + Type = type; + Cursor = new Cursor(type); + } + + public StandardCursorType Type { get; } + public Cursor Cursor { get; } + } + } +} From 28fa72185c3f71a1e590fd886bada1d38d3527d6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 8 Feb 2021 16:19:51 +0000 Subject: [PATCH 10/16] add native side implementation for osx. --- native/Avalonia.Native/src/OSX/cursor.mm | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/cursor.mm b/native/Avalonia.Native/src/OSX/cursor.mm index b6f9ed5071..1732d6e71f 100644 --- a/native/Avalonia.Native/src/OSX/cursor.mm +++ b/native/Avalonia.Native/src/OSX/cursor.mm @@ -62,6 +62,28 @@ public: return S_OK; } + + virtual HRESULT CreateCustomCursor (void* bitmapData, size_t length, AvnPixelSize hotPixel, IAvnCursor** retOut) override + { + if(bitmapData == nullptr || retOut == nullptr) + { + return E_POINTER; + } + + NSData *imageData = [NSData dataWithBytes:bitmapData length:length]; + NSImage *image = [[NSImage alloc] initWithData:imageData]; + + + NSPoint hotSpot; + hotSpot.x = hotPixel.Width; + hotSpot.y = hotPixel.Height; + + *retOut = new Cursor([[NSCursor new] initWithImage: image hotSpot: hotSpot]); + + (*retOut)->AddRef(); + + return S_OK; + } }; extern IAvnCursorFactory* CreateCursorFactory() From 304c49821cab0cfa92de90c1e09026e96fa90406 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 8 Feb 2021 17:12:56 +0000 Subject: [PATCH 11/16] fix control catalog. --- samples/ControlCatalog/ViewModels/CursorPageViewModel.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs b/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs index a4c643dd04..f1cc0637dc 100644 --- a/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs @@ -5,11 +5,11 @@ using Avalonia; using Avalonia.Input; using Avalonia.Media.Imaging; using Avalonia.Platform; -using ReactiveUI; +using MiniMvvm; namespace ControlCatalog.ViewModels { - public class CursorPageViewModel : ReactiveObject + public class CursorPageViewModel : ViewModelBase { public CursorPageViewModel() { @@ -25,6 +25,7 @@ namespace ControlCatalog.ViewModels } public IEnumerable StandardCursors { get; } + public Cursor CustomCursor { get; } public class StandardCursorModel @@ -36,6 +37,7 @@ namespace ControlCatalog.ViewModels } public StandardCursorType Type { get; } + public Cursor Cursor { get; } } } From e90fe543f0466602687dbea6f568a8f42a0832b3 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 8 Feb 2021 17:16:46 +0000 Subject: [PATCH 12/16] add full osx implementation for custom cursors. --- src/Avalonia.Native/Cursor.cs | 18 ++++++++++++++++-- src/Avalonia.Native/avn.idl | 1 + 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Native/Cursor.cs b/src/Avalonia.Native/Cursor.cs index 2cb085bc7e..ae218e270c 100644 --- a/src/Avalonia.Native/Cursor.cs +++ b/src/Avalonia.Native/Cursor.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using Avalonia.Input; using Avalonia.Platform; using Avalonia.Native.Interop; @@ -39,9 +40,22 @@ namespace Avalonia.Native return new AvaloniaNativeCursor( cursor ); } - public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + public unsafe ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) { - throw new NotImplementedException(); + using(var ms = new MemoryStream()) + { + cursor.Save(ms); + + var imageData = ms.ToArray(); + + fixed(void* ptr = imageData) + { + var avnCursor = _native.CreateCustomCursor(ptr, new IntPtr(imageData.Length), + new AvnPixelSize { Width = hotSpot.X, Height = hotSpot.Y }); + + return new AvaloniaNativeCursor(avnCursor); + } + } } } } diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 166046ca24..3627ff6894 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -619,6 +619,7 @@ interface IAvnCursor : IUnknown interface IAvnCursorFactory : IUnknown { HRESULT GetCursor(AvnStandardCursorType cursorType, IAvnCursor** retOut); + HRESULT CreateCustomCursor (void* bitmapData, size_t length, AvnPixelSize hotPixel, IAvnCursor** retOut); } [uuid(60452465-8616-40af-bc00-042e69828ce7)] From 1d7690c05ddf65d83aec4023704c87ee44a7a505 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 8 Feb 2021 18:51:10 +0000 Subject: [PATCH 13/16] Api changes. --- src/Avalonia.Controls/ApiCompatBaseline.txt | 6 ++++++ src/Avalonia.Input/ApiCompatBaseline.txt | 4 ++++ 2 files changed, 10 insertions(+) create mode 100644 src/Avalonia.Controls/ApiCompatBaseline.txt create mode 100644 src/Avalonia.Input/ApiCompatBaseline.txt diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt new file mode 100644 index 0000000000..e5adc8c6ed --- /dev/null +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -0,0 +1,6 @@ +Compat issues with assembly Avalonia.Controls: +MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. +MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. +Total Issues: 4 diff --git a/src/Avalonia.Input/ApiCompatBaseline.txt b/src/Avalonia.Input/ApiCompatBaseline.txt new file mode 100644 index 0000000000..fff2fb2806 --- /dev/null +++ b/src/Avalonia.Input/ApiCompatBaseline.txt @@ -0,0 +1,4 @@ +Compat issues with assembly Avalonia.Input: +MembersMustExist : Member 'public Avalonia.Platform.IPlatformHandle Avalonia.Input.Cursor.PlatformCursor.get()' does not exist in the implementation but it does exist in the contract. +TypesMustExist : Type 'Avalonia.Platform.IStandardCursorFactory' does not exist in the implementation but it does exist in the contract. +Total Issues: 2 From 4e3af8c9c1c5a4d79c6bdbf232264319016b2a1a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Feb 2021 11:24:51 +0100 Subject: [PATCH 14/16] Fix non-compiling assemblies. --- src/iOS/Avalonia.iOS/Stubs.cs | 12 +++++++++--- tests/Avalonia.Benchmarks/NullCursorFactory.cs | 9 ++++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/iOS/Avalonia.iOS/Stubs.cs b/src/iOS/Avalonia.iOS/Stubs.cs index a35b301a7f..c2526d7d9f 100644 --- a/src/iOS/Avalonia.iOS/Stubs.cs +++ b/src/iOS/Avalonia.iOS/Stubs.cs @@ -5,9 +5,15 @@ using Avalonia.Platform; namespace Avalonia.iOS { - class CursorFactoryStub : IStandardCursorFactory + class CursorFactoryStub : ICursorFactory { - public IPlatformHandle GetCursor(StandardCursorType cursorType) => new PlatformHandle(IntPtr.Zero, "NULL"); + public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new CursorImplStub(); + ICursorImpl ICursorFactory.GetCursor(StandardCursorType cursorType) => new CursorImplStub(); + + private class CursorImplStub : ICursorImpl + { + public void Dispose() { } + } } class WindowingPlatformStub : IWindowingPlatform @@ -57,4 +63,4 @@ namespace Avalonia.iOS _ms.CopyTo(outputStream); } } -} \ No newline at end of file +} diff --git a/tests/Avalonia.Benchmarks/NullCursorFactory.cs b/tests/Avalonia.Benchmarks/NullCursorFactory.cs index 012adce0f2..9aeb353151 100644 --- a/tests/Avalonia.Benchmarks/NullCursorFactory.cs +++ b/tests/Avalonia.Benchmarks/NullCursorFactory.cs @@ -4,11 +4,14 @@ using Avalonia.Platform; namespace Avalonia.Benchmarks { - internal class NullCursorFactory : IStandardCursorFactory + internal class NullCursorFactory : ICursorFactory { - public IPlatformHandle GetCursor(StandardCursorType cursorType) + public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new NullCursorImpl(); + ICursorImpl ICursorFactory.GetCursor(StandardCursorType cursorType) => new NullCursorImpl(); + + private class NullCursorImpl : ICursorImpl { - return new PlatformHandle(IntPtr.Zero, "null"); + public void Dispose() { } } } } From e1051b7ce00d25359567e3b290b516c0f4174d3b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Feb 2021 11:39:09 +0100 Subject: [PATCH 15/16] Fix another compiler error. --- src/iOS/Avalonia.iOS/AvaloniaView.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 7d367c99d1..36a70ea410 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -74,7 +74,7 @@ namespace Avalonia.iOS public PixelPoint PointToScreen(Point point) => new PixelPoint((int) point.X, (int) point.Y); - public void SetCursor(IPlatformHandle cursor) + public void SetCursor(ICursorImpl _) { // no-op } @@ -136,4 +136,4 @@ namespace Avalonia.iOS set => _topLevel.Content = value; } } -} \ No newline at end of file +} From ef52b6cfd7dc07c0e69906b528fe71d8a7f0b189 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Feb 2021 13:11:24 +0100 Subject: [PATCH 16/16] And another compiler error. --- src/iOS/Avalonia.iOS/Platform.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs index 28bccb6637..2cac5e6bcf 100644 --- a/src/iOS/Avalonia.iOS/Platform.cs +++ b/src/iOS/Avalonia.iOS/Platform.cs @@ -27,7 +27,7 @@ namespace Avalonia.iOS var softKeyboard = new SoftKeyboardHelper(); AvaloniaLocator.CurrentMutable .Bind().ToConstant(GlFeature) - .Bind().ToConstant(new CursorFactoryStub()) + .Bind().ToConstant(new CursorFactoryStub()) .Bind().ToConstant(new WindowingPlatformStub()) .Bind().ToConstant(new ClipboardImpl()) .Bind().ToConstant(new PlatformSettings())