Browse Source

Merge branch 'master' into fixes/LineBreakEnumerator

pull/5410/head
Dariusz Komosiński 5 years ago
committed by GitHub
parent
commit
eaffccb0ac
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      native/Avalonia.Native/src/OSX/cursor.mm
  2. BIN
      samples/ControlCatalog/Assets/avalonia-32.png
  3. 4
      samples/ControlCatalog/MainView.xaml
  4. 29
      samples/ControlCatalog/Pages/CursorPage.xaml
  5. 20
      samples/ControlCatalog/Pages/CursorPage.xaml.cs
  6. 44
      samples/ControlCatalog/ViewModels/CursorPageViewModel.cs
  7. 6
      src/Avalonia.Controls/ApiCompatBaseline.txt
  8. 2
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  9. 2
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  10. 2
      src/Avalonia.Controls/TopLevel.cs
  11. 2
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
  12. 12
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  13. 2
      src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
  14. 8
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  15. 2
      src/Avalonia.Headless/HeadlessWindowImpl.cs
  16. 4
      src/Avalonia.Input/ApiCompatBaseline.txt
  17. 39
      src/Avalonia.Input/Cursor.cs
  18. 12
      src/Avalonia.Input/Platform/ICursorFactory.cs
  19. 14
      src/Avalonia.Input/Platform/ICursorImpl.cs
  20. 9
      src/Avalonia.Input/Platform/IStandardCursorFactory.cs
  21. 2
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  22. 25
      src/Avalonia.Native/Cursor.cs
  23. 6
      src/Avalonia.Native/WindowImplBase.cs
  24. 1
      src/Avalonia.Native/avn.idl
  25. 73
      src/Avalonia.X11/X11CursorFactory.cs
  26. 2
      src/Avalonia.X11/X11Platform.cs
  27. 2
      src/Avalonia.X11/X11Structs.cs
  28. 8
      src/Avalonia.X11/X11Window.cs
  29. 7
      src/Avalonia.X11/XLib.cs
  30. 2
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  31. 2
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  32. 9
      src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs
  33. 6
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  34. 127
      src/Windows/Avalonia.Win32/CursorFactory.cs
  35. 16
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  36. 2
      src/Windows/Avalonia.Win32/Win32Platform.cs
  37. 15
      src/Windows/Avalonia.Win32/WindowImpl.cs
  38. 4
      src/iOS/Avalonia.iOS/AvaloniaView.cs
  39. 2
      src/iOS/Avalonia.iOS/Platform.cs
  40. 12
      src/iOS/Avalonia.iOS/Stubs.cs
  41. 9
      tests/Avalonia.Benchmarks/NullCursorFactory.cs
  42. 2
      tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs
  43. 19
      tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs
  44. 2
      tests/Avalonia.Controls.UnitTests/DatePickerTests.cs
  45. 4
      tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs
  46. 4
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
  47. 2
      tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs
  48. 2
      tests/Avalonia.Controls.UnitTests/TimePickerTests.cs
  49. 8
      tests/Avalonia.UnitTests/TestServices.cs
  50. 2
      tests/Avalonia.UnitTests/UnitTestApplication.cs

22
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()

BIN
samples/ControlCatalog/Assets/avalonia-32.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

4
samples/ControlCatalog/MainView.xaml

@ -22,6 +22,10 @@
<TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem>
<TabItem Header="ComboBox"><pages:ComboBoxPage/></TabItem>
<TabItem Header="ContextMenu"><pages:ContextMenuPage/></TabItem>
<TabItem Header="Cursor"
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<pages:CursorPage/>
</TabItem>
<TabItem Header="DataGrid"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">

29
samples/ControlCatalog/Pages/CursorPage.xaml

@ -0,0 +1,29 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CursorPage">
<Grid ColumnDefinitions="*,*" RowDefinitions="Auto,*">
<StackPanel Grid.ColumnSpan="2" Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Cursor</TextBlock>
<TextBlock Classes="h2">Defines a cursor (mouse pointer)</TextBlock>
</StackPanel>
<ListBox Grid.Row="1" Items="{Binding StandardCursors}" Margin="0 8 8 8">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Cursor" Value="{Binding Cursor}"/>
</Style>
</ListBox.Styles>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Type}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="1" Grid.Row="1" Margin="8 8 0 8">
<Button Cursor="{Binding CustomCursor}" Margin="0 8" Padding="16">
<TextBlock>Custom Cursor</TextBlock>
</Button>
</StackPanel>
</Grid>
</UserControl>

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

44
samples/ControlCatalog/ViewModels/CursorPageViewModel.cs

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.Input;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using MiniMvvm;
namespace ControlCatalog.ViewModels
{
public class CursorPageViewModel : ViewModelBase
{
public CursorPageViewModel()
{
StandardCursors = Enum.GetValues(typeof(StandardCursorType))
.Cast<StandardCursorType>()
.Select(x => new StandardCursorModel(x))
.ToList();
var loader = AvaloniaLocator.Current.GetService<IAssetLoader>();
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<StandardCursorModel> 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; }
}
}
}

6
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

2
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)
{
}

2
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@ -98,7 +98,7 @@ namespace Avalonia.Platform
/// Sets the cursor associated with the toplevel.
/// </summary>
/// <param name="cursor">The cursor. Use null for default cursor</param>
void SetCursor(IPlatformHandle cursor);
void SetCursor(ICursorImpl cursor);
/// <summary>
/// Gets or sets a method called when the underlying implementation is destroyed.

2
src/Avalonia.Controls/TopLevel.cs

@ -165,7 +165,7 @@ namespace Avalonia.Controls
this.GetObservable(PointerOverElementProperty)
.Select(
x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty<Cursor>())
.Switch().Subscribe(cursor => PlatformImpl?.SetCursor(cursor?.PlatformCursor));
.Switch().Subscribe(cursor => PlatformImpl?.SetCursor(cursor?.PlatformImpl));
if (((IStyleHost)this).StylingParent is IResourceHost applicationResources)
{

2
src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs

@ -47,7 +47,7 @@ namespace Avalonia.DesignerSupport.Remote
var threading = new InternalPlatformThreadingInterface();
AvaloniaLocator.CurrentMutable
.Bind<IClipboard>().ToSingleton<ClipboardStub>()
.Bind<IStandardCursorFactory>().ToSingleton<CursorFactoryStub>()
.Bind<ICursorFactory>().ToSingleton<CursorFactoryStub>()
.Bind<IKeyboardDevice>().ToConstant(Keyboard)
.Bind<IPlatformSettings>().ToConstant(instance)
.Bind<IPlatformThreadingInterface>().ToConstant(threading)

12
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)
{
}
@ -192,9 +192,15 @@ namespace Avalonia.DesignerSupport.Remote
public Task<object> GetDataAsync(string format) => Task.FromResult((object)null);
}
class CursorFactoryStub : IStandardCursorFactory
class CursorFactoryStub : ICursorFactory
{
public IPlatformHandle GetCursor(StandardCursorType cursorType) => 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

2
src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs

@ -58,7 +58,7 @@ namespace Avalonia.Headless
AvaloniaLocator.CurrentMutable
.Bind<IPlatformThreadingInterface>().ToConstant(new HeadlessPlatformThreadingInterface())
.Bind<IClipboard>().ToSingleton<HeadlessClipboardStub>()
.Bind<IStandardCursorFactory>().ToSingleton<HeadlessCursorFactoryStub>()
.Bind<ICursorFactory>().ToSingleton<HeadlessCursorFactoryStub>()
.Bind<IPlatformSettings>().ToConstant(new HeadlessPlatformSettingsStub())
.Bind<ISystemDialogImpl>().ToSingleton<HeadlessSystemDialogsStub>()
.Bind<IPlatformIconLoader>().ToSingleton<HeadlessIconLoaderStub>()

8
src/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -52,12 +52,14 @@ namespace Avalonia.Headless
}
}
class HeadlessCursorFactoryStub : IStandardCursorFactory
class HeadlessCursorFactoryStub : ICursorFactory
{
public ICursorImpl GetCursor(StandardCursorType cursorType) => new CursorStub();
public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new CursorStub();
public IPlatformHandle GetCursor(StandardCursorType cursorType)
private class CursorStub : ICursorImpl
{
return new PlatformHandle(new IntPtr((int)cursorType), "STUB");
public void Dispose() { }
}
}

2
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)
{
}

4
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

39
src/Avalonia.Input/Cursors.cs → src/Avalonia.Input/Cursor.cs

@ -1,15 +1,11 @@
using System;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
#nullable enable
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,
@ -46,21 +42,28 @@ namespace Avalonia.Input
// SizeNorthEastSouthWest,
}
public class Cursor
public class Cursor : IDisposable
{
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)
: this(GetCursor(cursorType))
: this(GetCursorFactory().GetCursor(cursorType))
{
}
public Cursor(IBitmap cursor, PixelPoint hotSpot)
: this(GetCursorFactory().CreateCursor(cursor.PlatformImpl.Item, hotSpot))
{
}
public IPlatformHandle PlatformCursor { get; }
public ICursorImpl PlatformImpl { get; }
public void Dispose() => PlatformImpl.Dispose();
public static Cursor Parse(string s)
{
@ -69,16 +72,10 @@ namespace Avalonia.Input
throw new ArgumentException($"Unrecognized cursor type '{s}'.");
}
private static IPlatformHandle GetCursor(StandardCursorType type)
private static ICursorFactory 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<ICursorFactory>() ??
throw new Exception("Could not create Cursor: ICursorFactory not registered.");
}
}
}

12
src/Avalonia.Input/Platform/ICursorFactory.cs

@ -0,0 +1,12 @@
using Avalonia.Input;
#nullable enable
namespace Avalonia.Platform
{
public interface ICursorFactory
{
ICursorImpl GetCursor(StandardCursorType cursorType);
ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot);
}
}

14
src/Avalonia.Input/Platform/ICursorImpl.cs

@ -0,0 +1,14 @@
using System;
using Avalonia.Input;
#nullable enable
namespace Avalonia.Platform
{
/// <summary>
/// Represents a platform implementation of a <see cref="Cursor"/>.
/// </summary>
public interface ICursorImpl : IDisposable
{
}
}

9
src/Avalonia.Input/Platform/IStandardCursorFactory.cs

@ -1,9 +0,0 @@
using Avalonia.Input;
namespace Avalonia.Platform
{
public interface IStandardCursorFactory
{
IPlatformHandle GetCursor(StandardCursorType cursorType);
}
}

2
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -97,7 +97,7 @@ namespace Avalonia.Native
AvaloniaLocator.CurrentMutable
.Bind<IPlatformThreadingInterface>()
.ToConstant(new PlatformThreadingInterface(_factory.CreatePlatformThreadingInterface()))
.Bind<IStandardCursorFactory>().ToConstant(new CursorFactory(_factory.CreateCursorFactory()))
.Bind<ICursorFactory>().ToConstant(new CursorFactory(_factory.CreateCursorFactory()))
.Bind<IPlatformIconLoader>().ToSingleton<IconLoader>()
.Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
.Bind<IPlatformSettings>().ToConstant(this)

25
src/Avalonia.Native/Cursor.cs

@ -1,11 +1,12 @@
using System;
using System.IO;
using Avalonia.Input;
using Avalonia.Platform;
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;
@ -24,7 +25,7 @@ namespace Avalonia.Native
}
}
class CursorFactory : IStandardCursorFactory
class CursorFactory : ICursorFactory
{
IAvnCursorFactory _native;
@ -33,10 +34,28 @@ 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 unsafe ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot)
{
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);
}
}
}
}
}

6
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<IKeyboardDevice>();
_mouse = new MouseDevice();
_cursorFactory = AvaloniaLocator.Current.GetService<IStandardCursorFactory>();
_cursorFactory = AvaloniaLocator.Current.GetService<ICursorFactory>();
}
protected void Init(IAvnWindowBase window, IAvnScreens screens, IGlContext glContext)
@ -398,7 +398,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)
{

1
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)]

73
src/Avalonia.X11/X11CursorFactory.cs

@ -1,12 +1,17 @@
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
{
class X11CursorFactory : IStandardCursorFactory
class X11CursorFactory : ICursorFactory
{
private static readonly byte[] NullCursorData = new byte[] { 0 };
@ -51,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)
@ -64,7 +69,12 @@ namespace Avalonia.X11
? _cursors[shape]
: _cursors[CursorFontShape.XC_top_left_arrow];
}
return new PlatformHandle(handle, "XCURSOR");
return new CursorImpl(handle);
}
public unsafe ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot)
{
return new XImageCursor(_display, cursor, hotSpot);
}
private static IntPtr GetNullCursor(IntPtr display)
@ -74,5 +84,62 @@ 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 : CursorImpl, IFramebufferPlatformSurface, IPlatformHandle
{
private readonly PixelSize _pixelSize;
private readonly IUnmanagedBlob _blob;
public XImageCursor(IntPtr display, IBitmapImpl bitmap, PixelPoint hotSpot)
{
var size = Marshal.SizeOf<XcursorImage>() +
(bitmap.PixelSize.Width * bitmap.PixelSize.Height * 4);
_pixelSize = bitmap.PixelSize;
_blob = AvaloniaLocator.Current.GetService<IRuntimePlatform>().AllocBlob(size);
var image = (XcursorImage*)_blob.Address;
image->version = 1;
image->size = Marshal.SizeOf<XcursorImage>();
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<IPlatformRenderInterface>().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 string HandleDescriptor => "XCURSOR";
public override void Dispose()
{
XLib.XcursorImageDestroy(Handle);
_blob.Dispose();
}
public ILockedFramebuffer Lock()
{
return new LockedFramebuffer(
_blob.Address + Marshal.SizeOf<XcursorImage>(),
_pixelSize, _pixelSize.Width * 4,
new Vector(96, 96), PixelFormat.Bgra8888, null);
}
}
}
class CursorImpl : ICursorImpl
{
public CursorImpl() { }
public CursorImpl(IntPtr handle) => Handle = handle;
public IntPtr Handle { get; protected set; }
public virtual void Dispose() { }
}
}

2
src/Avalonia.X11/X11Platform.cs

@ -71,7 +71,7 @@ namespace Avalonia.X11
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Control))
.Bind<IKeyboardDevice>().ToFunc(() => KeyboardDevice)
.Bind<IStandardCursorFactory>().ToConstant(new X11CursorFactory(Display))
.Bind<ICursorFactory>().ToConstant(new X11CursorFactory(Display))
.Bind<IClipboard>().ToConstant(new X11Clipboard(this))
.Bind<IPlatformSettings>().ToConstant(new PlatformSettingsStub())
.Bind<IPlatformIconLoader>().ToConstant(new X11IconLoader(Info))

2
src/Avalonia.X11/X11Structs.cs

@ -1693,7 +1693,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 */

8
src/Avalonia.X11/X11Window.cs

@ -872,15 +872,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);
}
}

7
src/Avalonia.X11/XLib.cs

@ -20,6 +20,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);
@ -569,6 +570,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);

2
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)
{
}

2
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -38,7 +38,7 @@ namespace Avalonia.LinuxFramebuffer
.Bind<IPlatformThreadingInterface>().ToConstant(Threading)
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IStandardCursorFactory>().ToTransient<CursorFactoryStub>()
.Bind<ICursorFactory>().ToTransient<CursorFactoryStub>()
.Bind<IKeyboardDevice>().ToConstant(new KeyboardDevice())
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
.Bind<IRenderLoop>().ToConstant(new RenderLoop())

9
src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs

@ -4,11 +4,14 @@ using Avalonia.Platform;
namespace Avalonia.LinuxFramebuffer
{
internal class CursorFactoryStub : IStandardCursorFactory
internal class CursorFactoryStub : ICursorFactory
{
public IPlatformHandle GetCursor(StandardCursorType cursorType)
public ICursorImpl GetCursor(StandardCursorType cursorType) => new CursorStub();
public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new CursorStub();
private class CursorStub : ICursorImpl
{
return new PlatformHandle(IntPtr.Zero, null);
public void Dispose() { }
}
}
internal class PlatformSettings : IPlatformSettings

6
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<RawInputEventArgs> ITopLevelImpl.Input { get; set; } //TODO

127
src/Windows/Avalonia.Win32/CursorFactory.cs

@ -1,12 +1,17 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
using SdBitmap = System.Drawing.Bitmap;
using SdPixelFormat = System.Drawing.Imaging.PixelFormat;
namespace Avalonia.Win32
{
internal class CursorFactory : IStandardCursorFactory
internal class CursorFactory : ICursorFactory
{
public static CursorFactory Instance { get; } = new CursorFactory();
@ -29,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));
}
}
}
@ -70,22 +74,119 @@ namespace Avalonia.Win32
{StandardCursorType.DragLink, 32516},
};
private static readonly Dictionary<StandardCursorType, IPlatformHandle> Cache =
new Dictionary<StandardCursorType, IPlatformHandle>();
private static readonly Dictionary<StandardCursorType, CursorImpl> Cache =
new Dictionary<StandardCursorType, CursorImpl>();
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 ICursorImpl 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 CursorImpl(UnmanagedMethods.CreateIconIndirect(ref info), true);
}
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.Format32bppPArgb)
{
throw new NotSupportedException(
"Images with premultiplied alpha not yet supported as cursor images.");
}
if (source.PixelFormat != SdPixelFormat.Format32bppArgb)
{
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);
}
}
}
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;
}
}
}
}

16
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -1035,6 +1035,12 @@ 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 DestroyIcon(IntPtr hIcon);
[DllImport("user32.dll")]
public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);
@ -1762,6 +1768,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
{

2
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -104,7 +104,7 @@ namespace Avalonia.Win32
Options = options;
AvaloniaLocator.CurrentMutable
.Bind<IClipboard>().ToSingleton<ClipboardImpl>()
.Bind<IStandardCursorFactory>().ToConstant(CursorFactory.Instance)
.Bind<ICursorFactory>().ToConstant(CursorFactory.Instance)
.Bind<IKeyboardDevice>().ToConstant(WindowsKeyboardDevice.Instance)
.Bind<IPlatformSettings>().ToConstant(s_instance)
.Bind<IPlatformThreadingInterface>().ToConstant(s_instance)

15
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -608,14 +608,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);
}
}
}

4
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;
}
}
}
}

2
src/iOS/Avalonia.iOS/Platform.cs

@ -27,7 +27,7 @@ namespace Avalonia.iOS
var softKeyboard = new SoftKeyboardHelper();
AvaloniaLocator.CurrentMutable
.Bind<IPlatformOpenGlInterface>().ToConstant(GlFeature)
.Bind<IStandardCursorFactory>().ToConstant(new CursorFactoryStub())
.Bind<ICursorFactory>().ToConstant(new CursorFactoryStub())
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub())
.Bind<IClipboard>().ToConstant(new ClipboardImpl())
.Bind<IPlatformSettings>().ToConstant(new PlatformSettings())

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

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

2
tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs

@ -74,7 +74,7 @@ namespace Avalonia.Controls.UnitTests
}
private static TestServices Services => TestServices.MockThreadingInterface.With(
standardCursorFactory: Mock.Of<IStandardCursorFactory>());
standardCursorFactory: Mock.Of<ICursorFactory>());
private CalendarDatePicker CreateControl()
{

19
tests/Avalonia.Controls.UnitTests/CursorFactoryMock.cs

@ -1,14 +1,25 @@
using System;
using Avalonia.Input;
using Avalonia.Platform;
namespace Avalonia.Controls.UnitTests
{
public class CursorFactoryMock : IStandardCursorFactory
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 ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot)
{
return new MockCursorImpl();
}
private class MockCursorImpl : ICursorImpl
{
public void Dispose()
{
}
}
}
}

2
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<IStandardCursorFactory>(),
standardCursorFactory: Mock.Of<ICursorFactory>(),
textShaperImpl: new MockTextShaperImpl());
private IControlTemplate CreateTemplate()

4
tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs

@ -11,8 +11,8 @@ namespace Avalonia.Controls.UnitTests
{
public GridSplitterTests()
{
var cursorFactoryImpl = new Mock<IStandardCursorFactory>();
AvaloniaLocator.CurrentMutable.Bind<IStandardCursorFactory>().ToConstant(cursorFactoryImpl.Object);
var cursorFactoryImpl = new Mock<ICursorFactory>();
AvaloniaLocator.CurrentMutable.Bind<ICursorFactory>().ToConstant(cursorFactoryImpl.Object);
}
[Fact]

4
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<IStandardCursorFactory>());
standardCursorFactory: Mock.Of<ICursorFactory>());
private static TestServices Services => TestServices.MockThreadingInterface.With(
standardCursorFactory: Mock.Of<IStandardCursorFactory>());
standardCursorFactory: Mock.Of<ICursorFactory>());
private IControlTemplate CreateTemplate()
{

2
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<IStandardCursorFactory>());
standardCursorFactory: Mock.Of<ICursorFactory>());
private IControlTemplate CreateTemplate()
{

2
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<IStandardCursorFactory>(),
standardCursorFactory: Mock.Of<ICursorFactory>(),
textShaperImpl: new MockTextShaperImpl());
private IControlTemplate CreateTemplate()

8
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<IStandardCursorFactory>(),
standardCursorFactory: Mock.Of<ICursorFactory>(),
styler: new Styler(),
theme: () => CreateDefaultTheme(),
threadingInterface: Mock.Of<IPlatformThreadingInterface>(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<Styles> 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<Styles> 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<Styles> theme = null,
IPlatformThreadingInterface threadingInterface = null,

2
tests/Avalonia.UnitTests/UnitTestApplication.cs

@ -62,7 +62,7 @@ namespace Avalonia.UnitTests
.Bind<ITextShaperImpl>().ToConstant(Services.TextShaperImpl)
.Bind<IPlatformThreadingInterface>().ToConstant(Services.ThreadingInterface)
.Bind<IScheduler>().ToConstant(Services.Scheduler)
.Bind<IStandardCursorFactory>().ToConstant(Services.StandardCursorFactory)
.Bind<ICursorFactory>().ToConstant(Services.StandardCursorFactory)
.Bind<IStyler>().ToConstant(Services.Styler)
.Bind<IWindowingPlatform>().ToConstant(Services.WindowingPlatform)
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();

Loading…
Cancel
Save