From ca408e55b5091449158cfae7536960464389f6fd Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 20 Oct 2020 14:32:28 +0200 Subject: [PATCH] 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() + { + } } } }