Browse Source

Initial support for custom cursors on Win32.

pull/4887/head
Steven Kirk 6 years ago
parent
commit
03a18f56bb
  1. 1
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  2. 6
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  3. 20
      src/Avalonia.Input/Cursor.cs
  4. 1
      src/Avalonia.Input/Platform/IStandardCursorFactory.cs
  5. 5
      src/Avalonia.Native/Cursor.cs
  6. 5
      src/Avalonia.X11/X11CursorFactory.cs
  7. 5
      src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs
  8. 80
      src/Windows/Avalonia.Win32/CursorFactory.cs
  9. 13
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  10. 5
      tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs

1
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

6
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

20
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<IStandardCursorFactory>();
if (platform == null)
{
throw new Exception("Could not create Cursor: IStandardCursorFactory not registered.");
}
return platform.GetCursor(type);
return AvaloniaLocator.Current.GetService<IStandardCursorFactory>() ??
throw new Exception("Could not create Cursor: ICursorFactory not registered.");
}
}
}

1
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);
}
}

5
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();
}
}
}

5
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();

5
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
{

80
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);
}
}
}
}

13
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
{

5
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");
}
}
}

Loading…
Cancel
Save