[Mac] Implement NSCursor rectangles

This commit is contained in:
thefiddler 2014-04-30 08:38:19 +02:00
parent d013ef1868
commit 7d8f14baa7
2 changed files with 165 additions and 22 deletions

View file

@ -57,6 +57,15 @@ namespace OpenTK.Platform.MacOS
[DllImport(LibObjC, EntryPoint="objc_msgSend")]
public extern static IntPtr SendIntPtr(IntPtr receiver, IntPtr selector, IntPtr intPtr1, IntPtr intPtr2, IntPtr intPtr3);
[DllImport(LibObjC, EntryPoint="objc_msgSend")]
public extern static IntPtr SendIntPtr(IntPtr receiver, IntPtr selector, IntPtr p1, PointF p2);
[DllImport(LibObjC, EntryPoint="objc_msgSend")]
public extern static IntPtr SendIntPtr(IntPtr receiver, IntPtr selector, SizeF p1);
[DllImport(LibObjC, EntryPoint="objc_msgSend")]
public extern static IntPtr SendIntPtr(IntPtr receiver, IntPtr selector, RectangleF rectangle1);
[DllImport(LibObjC, EntryPoint="objc_msgSend")]
public extern static IntPtr SendIntPtr(IntPtr receiver, IntPtr selector, RectangleF rectangle1, int int1, int int2, bool bool1);
@ -66,6 +75,9 @@ namespace OpenTK.Platform.MacOS
[DllImport(LibObjC, EntryPoint="objc_msgSend")]
public extern static IntPtr SendIntPtr(IntPtr receiver, IntPtr selector, RectangleF rectangle1, int int1, IntPtr intPtr1, IntPtr intPtr2);
[DllImport(LibObjC, EntryPoint="objc_msgSend")]
public extern static IntPtr SendIntPtr(IntPtr receiver, IntPtr selector, IntPtr p1, int p2, int p3, int p4, int p5, int p6, int p7, IntPtr p8, int p9, int p10);
[DllImport(LibObjC, EntryPoint="objc_msgSend")]
public extern static bool SendBool(IntPtr receiver, IntPtr selector);
@ -99,6 +111,9 @@ namespace OpenTK.Platform.MacOS
[DllImport(LibObjC, EntryPoint="objc_msgSend")]
public extern static void SendVoid(IntPtr receiver, IntPtr selector, RectangleF rect1, bool bool1);
[DllImport(LibObjC, EntryPoint="objc_msgSend")]
public extern static void SendVoid(IntPtr receiver, IntPtr selector, RectangleF rect1, IntPtr intPtr1);
[DllImport(LibObjC, EntryPoint="objc_msgSend")]
public extern static int SendInt(IntPtr receiver, IntPtr selector);

View file

@ -31,6 +31,7 @@ using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading;
using OpenTK.Graphics;
using OpenTK.Input;
@ -87,6 +88,7 @@ namespace OpenTK.Platform.MacOS
static readonly IntPtr selAddTrackingArea = Selector.Get("addTrackingArea:");
static readonly IntPtr selRemoveTrackingArea = Selector.Get("removeTrackingArea:");
static readonly IntPtr selTrackingArea = Selector.Get("trackingArea");
static readonly IntPtr selInitWithSize = Selector.Get("initWithSize:");
static readonly IntPtr selInitWithRect = Selector.Get("initWithRect:options:owner:userInfo:");
static readonly IntPtr selOwner = Selector.Get("owner");
static readonly IntPtr selLocationInWindowOwner = Selector.Get("locationInWindow");
@ -112,9 +114,19 @@ namespace OpenTK.Platform.MacOS
//static readonly IntPtr selExitFullScreenModeWithOptions = Selector.Get("exitFullScreenModeWithOptions:");
//static readonly IntPtr selEnterFullScreenModeWithOptions = Selector.Get("enterFullScreenMode:withOptions:");
static readonly IntPtr selArrowCursor = Selector.Get("arrowCursor");
static readonly IntPtr selAddCursorRect = Selector.Get("addCursorRect:cursor:");
static readonly IntPtr selInvalidateCursorRectsForView = Selector.Get("invalidateCursorRectsForView:");
static readonly IntPtr selInitWithBitmapDataPlanes =
Selector.Get("initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bytesPerRow:bitsPerPixel:");
static readonly IntPtr selBitmapData = Selector.Get("bitmapData");
static readonly IntPtr selAddRepresentation = Selector.Get("addRepresentation:");
static readonly IntPtr selInitWithImageHotSpot = Selector.Get("initWithImage:hotSpot:");
static readonly IntPtr NSDefaultRunLoopMode;
static readonly IntPtr NSCursor;
static readonly IntPtr NSImage;
static readonly IntPtr NSBitmapImageRep;
static readonly IntPtr NSDeviceRGBColorSpace = Cocoa.ToNSString("NSDeviceRGBColorSpace");
static CocoaNativeWindow()
{
@ -122,6 +134,8 @@ namespace OpenTK.Platform.MacOS
NSApplication.Initialize(); // Problem: This does not allow creating a separate app and using CocoaNativeWindow.
NSDefaultRunLoopMode = Cocoa.GetStringConstant(Cocoa.FoundationLibrary, "NSDefaultRunLoopMode");
NSCursor = Class.Get("NSCursor");
NSImage = Class.Get("NSImage");
NSBitmapImageRep = Class.Get("NSBitmapImageRep");
}
private CocoaWindowInfo windowInfo;
@ -167,9 +181,12 @@ namespace OpenTK.Platform.MacOS
Class.RegisterMethod(windowClass, new AcceptsFirstResponderDelegate(AcceptsFirstResponder), "acceptsFirstResponder", "b@:");
Class.RegisterMethod(windowClass, new CanBecomeKeyWindowDelegate(CanBecomeKeyWindow), "canBecomeKeyWindow", "b@:");
Class.RegisterMethod(windowClass, new CanBecomeMainWindowDelegate(CanBecomeMainWindow), "canBecomeMainWindow", "b@:");
Class.RegisterClass(windowClass);
IntPtr viewClass = Class.AllocateClass("OpenTK_NSView" + UniqueId, "NSView");
Class.RegisterMethod(viewClass, new ResetCursorRectsDelegate(ResetCursorRects), "resetCursorRects", "v@:");
Class.RegisterClass(viewClass);
// Create window instance
var contentRect = new System.Drawing.RectangleF(x, y, width, height);
var style = GetStyleMask(windowBorder);
@ -178,6 +195,22 @@ namespace OpenTK.Platform.MacOS
IntPtr windowPtr;
windowPtr = Cocoa.SendIntPtr(windowClass, Selector.Alloc);
windowPtr = Cocoa.SendIntPtr(windowPtr, Selector.Get("initWithContentRect:styleMask:backing:defer:"), contentRect, (int)style, (int)bufferingType, false);
// Replace view with our custom implementation
// that overrides resetCursorRects (maybe there is
// a better way to implement this override?)
// Existing view:
IntPtr viewPtr = Cocoa.SendIntPtr(windowPtr, Selector.Get("contentView"));
// Our custom view with the same bounds:
viewPtr = Cocoa.SendIntPtr(
Cocoa.SendIntPtr(viewClass, Selector.Alloc),
Selector.Get("initWithFrame:"),
Cocoa.SendRect(viewPtr, selBounds));
if (viewPtr != IntPtr.Zero)
{
Cocoa.SendVoid(windowPtr, Selector.Get("setContentView:"), viewPtr);
}
windowInfo = new CocoaWindowInfo(windowPtr);
// Set up behavior
@ -205,6 +238,7 @@ namespace OpenTK.Platform.MacOS
delegate bool AcceptsFirstResponderDelegate(IntPtr self, IntPtr cmd);
delegate bool CanBecomeKeyWindowDelegate(IntPtr self, IntPtr cmd);
delegate bool CanBecomeMainWindowDelegate(IntPtr self, IntPtr cmd);
delegate void ResetCursorRectsDelegate(IntPtr self, IntPtr cmd);
private void WindowKeyDown(IntPtr self, IntPtr cmd, IntPtr notification)
{
@ -331,7 +365,11 @@ namespace OpenTK.Platform.MacOS
}
var ownerBounds = Cocoa.SendRect(owner, selBounds);
var options = (int)(NSTrackingAreaOptions.MouseEnteredAndExited | NSTrackingAreaOptions.ActiveInKeyWindow | NSTrackingAreaOptions.MouseMoved);
var options = (int)(
NSTrackingAreaOptions.MouseEnteredAndExited |
NSTrackingAreaOptions.ActiveInKeyWindow |
NSTrackingAreaOptions.MouseMoved |
NSTrackingAreaOptions.CursorUpdate);
trackingArea = Cocoa.SendIntPtr(Cocoa.SendIntPtr(Class.Get("NSTrackingArea"), Selector.Alloc),
selInitWithRect, ownerBounds, options, owner, IntPtr.Zero);
@ -431,7 +469,7 @@ namespace OpenTK.Platform.MacOS
{
if (selectedCursor != MouseCursor.Default)
{
SetCursor(selectedCursor);
//SetCursor(selectedCursor);
}
cursorInsideWindow = true;
@ -473,19 +511,13 @@ namespace OpenTK.Platform.MacOS
MathHelper.Clamp((int)Math.Round(rf.X), 0, Width),
MathHelper.Clamp((int)Math.Round(Height - rf.Y), 0, Height));
if (p.X < 0)
p.X = 0;
if (p.Y < 0)
p.Y = 0;
if (p.X > Width)
p.X = Width;
if (p.Y > Height)
p.Y = Height;
InputDriver.Mouse[0].Position = p;
}
break;
case NSEventType.CursorUpdate:
break;
case NSEventType.ScrollWheel:
{
var scrollingDelta = Cocoa.SendFloat(e, selScrollingDeltaY);
@ -912,19 +944,116 @@ namespace OpenTK.Platform.MacOS
}
set
{
// We only modify the cursor when it is
// inside the window and visible.
// If it is outside the window or invisible,
// we store the selected cursor and change it
// in the MouseEnter event.
if (CursorVisible && cursorInsideWindow)
{
SetCursor(value);
}
selectedCursor = value;
InvalidateCursorRects();
}
}
static IntPtr ToNSCursor(MouseCursor cursor)
{
// We need to allocate a NSBitmapImageRep, fill it with pixels
// and then convert it to a NSImage.
// According to the documentation, alpha-enabled formats should
// premultiply alpha, even though that "generally has negligible
// effect on output quality."
IntPtr imgdata =
Cocoa.SendIntPtr(
Cocoa.SendIntPtr(
Cocoa.SendIntPtr(NSBitmapImageRep, Selector.Alloc),
selInitWithBitmapDataPlanes,
IntPtr.Zero,
cursor.Width,
cursor.Height,
8,
4,
1,
0,
NSDeviceRGBColorSpace,
4 * cursor.Width,
32),
Selector.Autorelease);
if (imgdata == IntPtr.Zero)
{
Debug.Print("Failed to create NSBitmapImageRep with size ({0},{1]})",
cursor.Width, cursor.Height);
return IntPtr.Zero;
}
// Premultiply and copy the cursor data
int i = 0;
IntPtr data = Cocoa.SendIntPtr(imgdata, selBitmapData);
for (int y = 0; y < cursor.Height; y++)
{
for (int x = 0; x < cursor.Width; x++)
{
byte a = cursor.Argb[i];
byte r = (byte)((cursor.Argb[i + 1] * a) / 255);
byte g = (byte)((cursor.Argb[i + 2] * a) / 255);
byte b = (byte)((cursor.Argb[i + 3] * a) / 255);
Marshal.WriteByte(data, i++, a);
Marshal.WriteByte(data, i++, r);
Marshal.WriteByte(data, i++, g);
Marshal.WriteByte(data, i++, b);
}
}
// Construct the actual NSImage
IntPtr img =
Cocoa.SendIntPtr(
Cocoa.SendIntPtr(
Cocoa.SendIntPtr(NSImage, Selector.Alloc),
selInitWithSize,
new SizeF(cursor.Width, cursor.Height)),
Selector.Autorelease);
if (img == IntPtr.Zero)
{
Debug.Print("Failed to construct NSImage from NSBitmapImageRep");
return IntPtr.Zero;
}
Cocoa.SendVoid(img, selAddRepresentation, imgdata);
// Convert the NSImage to a NSCursor
IntPtr nscursor =
Cocoa.SendIntPtr(
Cocoa.SendIntPtr(
Cocoa.SendIntPtr(NSCursor, Selector.Alloc),
selInitWithImageHotSpot,
img,
new PointF(cursor.X, cursor.Y)
),
Selector.Autorelease);
return nscursor;
}
void ResetCursorRects(IntPtr sender, IntPtr cmd)
{
// We will add a new cursor rectangle that covers the complete view
var rect = Cocoa.SendRect(windowInfo.ViewHandle, selBounds);
// Inside this rectangle, the following NSCursor will be used
var cursor = IntPtr.Zero;
if (selectedCursor == MouseCursor.Default)
{
cursor = Cocoa.SendIntPtr(NSCursor, selArrowCursor);
}
else
{
cursor = ToNSCursor(selectedCursor);
}
// Setup the cursor rectangle
if (cursor != IntPtr.Zero)
{
Cocoa.SendVoid(sender, selAddCursorRect, rect, cursor);
}
}
void InvalidateCursorRects()
{
Cocoa.SendVoid(windowInfo.Handle, selInvalidateCursorRectsForView, windowInfo.ViewHandle);
}
public bool CursorVisible
{
get { return cursorVisible; }
@ -1018,7 +1147,6 @@ namespace OpenTK.Platform.MacOS
}
else
{
throw new NotImplementedException();
}
}