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()
+ {
+ }
}
}
}