Permalink
Browse files

[Mac] Implement NSCursor rectangles

  • Loading branch information...
thefiddler committed Apr 30, 2014
1 parent d013ef1 commit 7d8f14baa79da888254cc6c9d206a48c49e0e71c
Showing with 164 additions and 21 deletions.
  1. +15 −0 Source/OpenTK/Platform/MacOS/Cocoa/Cocoa.cs
  2. +149 −21 Source/OpenTK/Platform/MacOS/CocoaNativeWindow.cs
@@ -57,6 +57,15 @@ static class Cocoa
[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 @@ static class Cocoa
[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 @@ static class Cocoa
[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);
@@ -31,6 +31,7 @@
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 @@ class CocoaNativeWindow : INativeWindow
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,16 +114,28 @@ class CocoaNativeWindow : INativeWindow
//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()
{
Cocoa.Initialize();
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 @@ public CocoaNativeWindow(int x, int y, int width, int height, string title, Grap
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 @@ public CocoaNativeWindow(int x, int y, int width, int height, string title, Grap
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 @@ public CocoaNativeWindow(int x, int y, int width, int height, string title, Grap
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 @@ private void ResetTrackingArea()
}
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 @@ public void ProcessEvents()
{
if (selectedCursor != MouseCursor.Default)
{
- SetCursor(selectedCursor);
+ //SetCursor(selectedCursor);
}
cursorInsideWindow = true;
@@ -473,19 +511,13 @@ public void ProcessEvents()
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,17 +944,114 @@ public MouseCursor Cursor
}
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)
+ 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++)
{
- SetCursor(value);
+ 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);
}
- selectedCursor = value;
}
+
+ // 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
@@ -1018,7 +1147,6 @@ private void SetCursor(MouseCursor cursor)
}
else
{
- throw new NotImplementedException();
}
}

0 comments on commit 7d8f14b

Please sign in to comment.