diff options
Diffstat (limited to 'libs/glfw-3.3.8/src/cocoa_window.m')
-rw-r--r-- | libs/glfw-3.3.8/src/cocoa_window.m | 1934 |
1 files changed, 1934 insertions, 0 deletions
diff --git a/libs/glfw-3.3.8/src/cocoa_window.m b/libs/glfw-3.3.8/src/cocoa_window.m new file mode 100644 index 0000000..bbab6c4 --- /dev/null +++ b/libs/glfw-3.3.8/src/cocoa_window.m @@ -0,0 +1,1934 @@ +//======================================================================== +// GLFW 3.3 macOS - www.glfw.org +//------------------------------------------------------------------------ +// Copyright (c) 2009-2019 Camilla Löwy <elmindreda@glfw.org> +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would +// be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +//======================================================================== +// It is fine to use C99 in this file because it will not be built with VS +//======================================================================== + +#include "internal.h" + +#include <float.h> +#include <string.h> + +// HACK: This enum value is missing from framework headers on OS X 10.11 despite +// having been (according to documentation) added in Mac OS X 10.7 +#define NSWindowCollectionBehaviorFullScreenNone (1 << 9) + +// Returns whether the cursor is in the content area of the specified window +// +static GLFWbool cursorInContentArea(_GLFWwindow* window) +{ + const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; + return [window->ns.view mouse:pos inRect:[window->ns.view frame]]; +} + +// Hides the cursor if not already hidden +// +static void hideCursor(_GLFWwindow* window) +{ + if (!_glfw.ns.cursorHidden) + { + [NSCursor hide]; + _glfw.ns.cursorHidden = GLFW_TRUE; + } +} + +// Shows the cursor if not already shown +// +static void showCursor(_GLFWwindow* window) +{ + if (_glfw.ns.cursorHidden) + { + [NSCursor unhide]; + _glfw.ns.cursorHidden = GLFW_FALSE; + } +} + +// Updates the cursor image according to its cursor mode +// +static void updateCursorImage(_GLFWwindow* window) +{ + if (window->cursorMode == GLFW_CURSOR_NORMAL) + { + showCursor(window); + + if (window->cursor) + [(NSCursor*) window->cursor->ns.object set]; + else + [[NSCursor arrowCursor] set]; + } + else + hideCursor(window); +} + +// Apply chosen cursor mode to a focused window +// +static void updateCursorMode(_GLFWwindow* window) +{ + if (window->cursorMode == GLFW_CURSOR_DISABLED) + { + _glfw.ns.disabledCursorWindow = window; + _glfwPlatformGetCursorPos(window, + &_glfw.ns.restoreCursorPosX, + &_glfw.ns.restoreCursorPosY); + _glfwCenterCursorInContentArea(window); + CGAssociateMouseAndMouseCursorPosition(false); + } + else if (_glfw.ns.disabledCursorWindow == window) + { + _glfw.ns.disabledCursorWindow = NULL; + _glfwPlatformSetCursorPos(window, + _glfw.ns.restoreCursorPosX, + _glfw.ns.restoreCursorPosY); + // NOTE: The matching CGAssociateMouseAndMouseCursorPosition call is + // made in _glfwPlatformSetCursorPos as part of a workaround + } + + if (cursorInContentArea(window)) + updateCursorImage(window); +} + +// Make the specified window and its video mode active on its monitor +// +static void acquireMonitor(_GLFWwindow* window) +{ + _glfwSetVideoModeNS(window->monitor, &window->videoMode); + const CGRect bounds = CGDisplayBounds(window->monitor->ns.displayID); + const NSRect frame = NSMakeRect(bounds.origin.x, + _glfwTransformYNS(bounds.origin.y + bounds.size.height - 1), + bounds.size.width, + bounds.size.height); + + [window->ns.object setFrame:frame display:YES]; + + _glfwInputMonitorWindow(window->monitor, window); +} + +// Remove the window and restore the original video mode +// +static void releaseMonitor(_GLFWwindow* window) +{ + if (window->monitor->window != window) + return; + + _glfwInputMonitorWindow(window->monitor, NULL); + _glfwRestoreVideoModeNS(window->monitor); +} + +// Translates macOS key modifiers into GLFW ones +// +static int translateFlags(NSUInteger flags) +{ + int mods = 0; + + if (flags & NSEventModifierFlagShift) + mods |= GLFW_MOD_SHIFT; + if (flags & NSEventModifierFlagControl) + mods |= GLFW_MOD_CONTROL; + if (flags & NSEventModifierFlagOption) + mods |= GLFW_MOD_ALT; + if (flags & NSEventModifierFlagCommand) + mods |= GLFW_MOD_SUPER; + if (flags & NSEventModifierFlagCapsLock) + mods |= GLFW_MOD_CAPS_LOCK; + + return mods; +} + +// Translates a macOS keycode to a GLFW keycode +// +static int translateKey(unsigned int key) +{ + if (key >= sizeof(_glfw.ns.keycodes) / sizeof(_glfw.ns.keycodes[0])) + return GLFW_KEY_UNKNOWN; + + return _glfw.ns.keycodes[key]; +} + +// Translate a GLFW keycode to a Cocoa modifier flag +// +static NSUInteger translateKeyToModifierFlag(int key) +{ + switch (key) + { + case GLFW_KEY_LEFT_SHIFT: + case GLFW_KEY_RIGHT_SHIFT: + return NSEventModifierFlagShift; + case GLFW_KEY_LEFT_CONTROL: + case GLFW_KEY_RIGHT_CONTROL: + return NSEventModifierFlagControl; + case GLFW_KEY_LEFT_ALT: + case GLFW_KEY_RIGHT_ALT: + return NSEventModifierFlagOption; + case GLFW_KEY_LEFT_SUPER: + case GLFW_KEY_RIGHT_SUPER: + return NSEventModifierFlagCommand; + case GLFW_KEY_CAPS_LOCK: + return NSEventModifierFlagCapsLock; + } + + return 0; +} + +// Defines a constant for empty ranges in NSTextInputClient +// +static const NSRange kEmptyRange = { NSNotFound, 0 }; + + +//------------------------------------------------------------------------ +// Delegate for window related notifications +//------------------------------------------------------------------------ + +@interface GLFWWindowDelegate : NSObject +{ + _GLFWwindow* window; +} + +- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow; + +@end + +@implementation GLFWWindowDelegate + +- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow +{ + self = [super init]; + if (self != nil) + window = initWindow; + + return self; +} + +- (BOOL)windowShouldClose:(id)sender +{ + _glfwInputWindowCloseRequest(window); + return NO; +} + +- (void)windowDidResize:(NSNotification *)notification +{ + if (window->context.source == GLFW_NATIVE_CONTEXT_API) + [window->context.nsgl.object update]; + + if (_glfw.ns.disabledCursorWindow == window) + _glfwCenterCursorInContentArea(window); + + const int maximized = [window->ns.object isZoomed]; + if (window->ns.maximized != maximized) + { + window->ns.maximized = maximized; + _glfwInputWindowMaximize(window, maximized); + } + + const NSRect contentRect = [window->ns.view frame]; + const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; + + if (fbRect.size.width != window->ns.fbWidth || + fbRect.size.height != window->ns.fbHeight) + { + window->ns.fbWidth = fbRect.size.width; + window->ns.fbHeight = fbRect.size.height; + _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height); + } + + if (contentRect.size.width != window->ns.width || + contentRect.size.height != window->ns.height) + { + window->ns.width = contentRect.size.width; + window->ns.height = contentRect.size.height; + _glfwInputWindowSize(window, contentRect.size.width, contentRect.size.height); + } +} + +- (void)windowDidMove:(NSNotification *)notification +{ + if (window->context.source == GLFW_NATIVE_CONTEXT_API) + [window->context.nsgl.object update]; + + if (_glfw.ns.disabledCursorWindow == window) + _glfwCenterCursorInContentArea(window); + + int x, y; + _glfwPlatformGetWindowPos(window, &x, &y); + _glfwInputWindowPos(window, x, y); +} + +- (void)windowDidMiniaturize:(NSNotification *)notification +{ + if (window->monitor) + releaseMonitor(window); + + _glfwInputWindowIconify(window, GLFW_TRUE); +} + +- (void)windowDidDeminiaturize:(NSNotification *)notification +{ + if (window->monitor) + acquireMonitor(window); + + _glfwInputWindowIconify(window, GLFW_FALSE); +} + +- (void)windowDidBecomeKey:(NSNotification *)notification +{ + if (_glfw.ns.disabledCursorWindow == window) + _glfwCenterCursorInContentArea(window); + + _glfwInputWindowFocus(window, GLFW_TRUE); + updateCursorMode(window); +} + +- (void)windowDidResignKey:(NSNotification *)notification +{ + if (window->monitor && window->autoIconify) + _glfwPlatformIconifyWindow(window); + + _glfwInputWindowFocus(window, GLFW_FALSE); +} + +- (void)windowDidChangeOcclusionState:(NSNotification* )notification +{ + if ([window->ns.object occlusionState] & NSWindowOcclusionStateVisible) + window->ns.occluded = GLFW_FALSE; + else + window->ns.occluded = GLFW_TRUE; +} + +@end + + +//------------------------------------------------------------------------ +// Content view class for the GLFW window +//------------------------------------------------------------------------ + +@interface GLFWContentView : NSView <NSTextInputClient> +{ + _GLFWwindow* window; + NSTrackingArea* trackingArea; + NSMutableAttributedString* markedText; +} + +- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow; + +@end + +@implementation GLFWContentView + +- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow +{ + self = [super init]; + if (self != nil) + { + window = initWindow; + trackingArea = nil; + markedText = [[NSMutableAttributedString alloc] init]; + + [self updateTrackingAreas]; + [self registerForDraggedTypes:@[NSPasteboardTypeURL]]; + } + + return self; +} + +- (void)dealloc +{ + [trackingArea release]; + [markedText release]; + [super dealloc]; +} + +- (BOOL)isOpaque +{ + return [window->ns.object isOpaque]; +} + +- (BOOL)canBecomeKeyView +{ + return YES; +} + +- (BOOL)acceptsFirstResponder +{ + return YES; +} + +- (BOOL)wantsUpdateLayer +{ + return YES; +} + +- (void)updateLayer +{ + if (window->context.source == GLFW_NATIVE_CONTEXT_API) + [window->context.nsgl.object update]; + + _glfwInputWindowDamage(window); +} + +- (void)cursorUpdate:(NSEvent *)event +{ + updateCursorImage(window); +} + +- (BOOL)acceptsFirstMouse:(NSEvent *)event +{ + return YES; +} + +- (void)mouseDown:(NSEvent *)event +{ + _glfwInputMouseClick(window, + GLFW_MOUSE_BUTTON_LEFT, + GLFW_PRESS, + translateFlags([event modifierFlags])); +} + +- (void)mouseDragged:(NSEvent *)event +{ + [self mouseMoved:event]; +} + +- (void)mouseUp:(NSEvent *)event +{ + _glfwInputMouseClick(window, + GLFW_MOUSE_BUTTON_LEFT, + GLFW_RELEASE, + translateFlags([event modifierFlags])); +} + +- (void)mouseMoved:(NSEvent *)event +{ + if (window->cursorMode == GLFW_CURSOR_DISABLED) + { + const double dx = [event deltaX] - window->ns.cursorWarpDeltaX; + const double dy = [event deltaY] - window->ns.cursorWarpDeltaY; + + _glfwInputCursorPos(window, + window->virtualCursorPosX + dx, + window->virtualCursorPosY + dy); + } + else + { + const NSRect contentRect = [window->ns.view frame]; + // NOTE: The returned location uses base 0,1 not 0,0 + const NSPoint pos = [event locationInWindow]; + + _glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y); + } + + window->ns.cursorWarpDeltaX = 0; + window->ns.cursorWarpDeltaY = 0; +} + +- (void)rightMouseDown:(NSEvent *)event +{ + _glfwInputMouseClick(window, + GLFW_MOUSE_BUTTON_RIGHT, + GLFW_PRESS, + translateFlags([event modifierFlags])); +} + +- (void)rightMouseDragged:(NSEvent *)event +{ + [self mouseMoved:event]; +} + +- (void)rightMouseUp:(NSEvent *)event +{ + _glfwInputMouseClick(window, + GLFW_MOUSE_BUTTON_RIGHT, + GLFW_RELEASE, + translateFlags([event modifierFlags])); +} + +- (void)otherMouseDown:(NSEvent *)event +{ + _glfwInputMouseClick(window, + (int) [event buttonNumber], + GLFW_PRESS, + translateFlags([event modifierFlags])); +} + +- (void)otherMouseDragged:(NSEvent *)event +{ + [self mouseMoved:event]; +} + +- (void)otherMouseUp:(NSEvent *)event +{ + _glfwInputMouseClick(window, + (int) [event buttonNumber], + GLFW_RELEASE, + translateFlags([event modifierFlags])); +} + +- (void)mouseExited:(NSEvent *)event +{ + if (window->cursorMode == GLFW_CURSOR_HIDDEN) + showCursor(window); + + _glfwInputCursorEnter(window, GLFW_FALSE); +} + +- (void)mouseEntered:(NSEvent *)event +{ + if (window->cursorMode == GLFW_CURSOR_HIDDEN) + hideCursor(window); + + _glfwInputCursorEnter(window, GLFW_TRUE); +} + +- (void)viewDidChangeBackingProperties +{ + const NSRect contentRect = [window->ns.view frame]; + const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; + const float xscale = fbRect.size.width / contentRect.size.width; + const float yscale = fbRect.size.height / contentRect.size.height; + + if (xscale != window->ns.xscale || yscale != window->ns.yscale) + { + if (window->ns.retina && window->ns.layer) + [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]]; + + window->ns.xscale = xscale; + window->ns.yscale = yscale; + _glfwInputWindowContentScale(window, xscale, yscale); + } + + if (fbRect.size.width != window->ns.fbWidth || + fbRect.size.height != window->ns.fbHeight) + { + window->ns.fbWidth = fbRect.size.width; + window->ns.fbHeight = fbRect.size.height; + _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height); + } +} + +- (void)drawRect:(NSRect)rect +{ + _glfwInputWindowDamage(window); +} + +- (void)updateTrackingAreas +{ + if (trackingArea != nil) + { + [self removeTrackingArea:trackingArea]; + [trackingArea release]; + } + + const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | + NSTrackingActiveInKeyWindow | + NSTrackingEnabledDuringMouseDrag | + NSTrackingCursorUpdate | + NSTrackingInVisibleRect | + NSTrackingAssumeInside; + + trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] + options:options + owner:self + userInfo:nil]; + + [self addTrackingArea:trackingArea]; + [super updateTrackingAreas]; +} + +- (void)keyDown:(NSEvent *)event +{ + const int key = translateKey([event keyCode]); + const int mods = translateFlags([event modifierFlags]); + + _glfwInputKey(window, key, [event keyCode], GLFW_PRESS, mods); + + [self interpretKeyEvents:@[event]]; +} + +- (void)flagsChanged:(NSEvent *)event +{ + int action; + const unsigned int modifierFlags = + [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask; + const int key = translateKey([event keyCode]); + const int mods = translateFlags(modifierFlags); + const NSUInteger keyFlag = translateKeyToModifierFlag(key); + + if (keyFlag & modifierFlags) + { + if (window->keys[key] == GLFW_PRESS) + action = GLFW_RELEASE; + else + action = GLFW_PRESS; + } + else + action = GLFW_RELEASE; + + _glfwInputKey(window, key, [event keyCode], action, mods); +} + +- (void)keyUp:(NSEvent *)event +{ + const int key = translateKey([event keyCode]); + const int mods = translateFlags([event modifierFlags]); + _glfwInputKey(window, key, [event keyCode], GLFW_RELEASE, mods); +} + +- (void)scrollWheel:(NSEvent *)event +{ + double deltaX = [event scrollingDeltaX]; + double deltaY = [event scrollingDeltaY]; + + if ([event hasPreciseScrollingDeltas]) + { + deltaX *= 0.1; + deltaY *= 0.1; + } + + if (fabs(deltaX) > 0.0 || fabs(deltaY) > 0.0) + _glfwInputScroll(window, deltaX, deltaY); +} + +- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender +{ + // HACK: We don't know what to say here because we don't know what the + // application wants to do with the paths + return NSDragOperationGeneric; +} + +- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender +{ + const NSRect contentRect = [window->ns.view frame]; + // NOTE: The returned location uses base 0,1 not 0,0 + const NSPoint pos = [sender draggingLocation]; + _glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y); + + NSPasteboard* pasteboard = [sender draggingPasteboard]; + NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES}; + NSArray* urls = [pasteboard readObjectsForClasses:@[[NSURL class]] + options:options]; + const NSUInteger count = [urls count]; + if (count) + { + char** paths = calloc(count, sizeof(char*)); + + for (NSUInteger i = 0; i < count; i++) + paths[i] = _glfw_strdup([urls[i] fileSystemRepresentation]); + + _glfwInputDrop(window, (int) count, (const char**) paths); + + for (NSUInteger i = 0; i < count; i++) + free(paths[i]); + free(paths); + } + + return YES; +} + +- (BOOL)hasMarkedText +{ + return [markedText length] > 0; +} + +- (NSRange)markedRange +{ + if ([markedText length] > 0) + return NSMakeRange(0, [markedText length] - 1); + else + return kEmptyRange; +} + +- (NSRange)selectedRange +{ + return kEmptyRange; +} + +- (void)setMarkedText:(id)string + selectedRange:(NSRange)selectedRange + replacementRange:(NSRange)replacementRange +{ + [markedText release]; + if ([string isKindOfClass:[NSAttributedString class]]) + markedText = [[NSMutableAttributedString alloc] initWithAttributedString:string]; + else + markedText = [[NSMutableAttributedString alloc] initWithString:string]; +} + +- (void)unmarkText +{ + [[markedText mutableString] setString:@""]; +} + +- (NSArray*)validAttributesForMarkedText +{ + return [NSArray array]; +} + +- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range + actualRange:(NSRangePointer)actualRange +{ + return nil; +} + +- (NSUInteger)characterIndexForPoint:(NSPoint)point +{ + return 0; +} + +- (NSRect)firstRectForCharacterRange:(NSRange)range + actualRange:(NSRangePointer)actualRange +{ + const NSRect frame = [window->ns.view frame]; + return NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0); +} + +- (void)insertText:(id)string replacementRange:(NSRange)replacementRange +{ + NSString* characters; + NSEvent* event = [NSApp currentEvent]; + const int mods = translateFlags([event modifierFlags]); + const int plain = !(mods & GLFW_MOD_SUPER); + + if ([string isKindOfClass:[NSAttributedString class]]) + characters = [string string]; + else + characters = (NSString*) string; + + NSRange range = NSMakeRange(0, [characters length]); + while (range.length) + { + uint32_t codepoint = 0; + + if ([characters getBytes:&codepoint + maxLength:sizeof(codepoint) + usedLength:NULL + encoding:NSUTF32StringEncoding + options:0 + range:range + remainingRange:&range]) + { + if (codepoint >= 0xf700 && codepoint <= 0xf7ff) + continue; + + _glfwInputChar(window, codepoint, mods, plain); + } + } +} + +- (void)doCommandBySelector:(SEL)selector +{ +} + +@end + + +//------------------------------------------------------------------------ +// GLFW window class +//------------------------------------------------------------------------ + +@interface GLFWWindow : NSWindow {} +@end + +@implementation GLFWWindow + +- (BOOL)canBecomeKeyWindow +{ + // Required for NSWindowStyleMaskBorderless windows + return YES; +} + +- (BOOL)canBecomeMainWindow +{ + return YES; +} + +@end + + +// Create the Cocoa window +// +static GLFWbool createNativeWindow(_GLFWwindow* window, + const _GLFWwndconfig* wndconfig, + const _GLFWfbconfig* fbconfig) +{ + window->ns.delegate = [[GLFWWindowDelegate alloc] initWithGlfwWindow:window]; + if (window->ns.delegate == nil) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Cocoa: Failed to create window delegate"); + return GLFW_FALSE; + } + + NSRect contentRect; + + if (window->monitor) + { + GLFWvidmode mode; + int xpos, ypos; + + _glfwPlatformGetVideoMode(window->monitor, &mode); + _glfwPlatformGetMonitorPos(window->monitor, &xpos, &ypos); + + contentRect = NSMakeRect(xpos, ypos, mode.width, mode.height); + } + else + contentRect = NSMakeRect(0, 0, wndconfig->width, wndconfig->height); + + NSUInteger styleMask = NSWindowStyleMaskMiniaturizable; + + if (window->monitor || !window->decorated) + styleMask |= NSWindowStyleMaskBorderless; + else + { + styleMask |= (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable); + + if (window->resizable) + styleMask |= NSWindowStyleMaskResizable; + } + + window->ns.object = [[GLFWWindow alloc] + initWithContentRect:contentRect + styleMask:styleMask + backing:NSBackingStoreBuffered + defer:NO]; + + if (window->ns.object == nil) + { + _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create window"); + return GLFW_FALSE; + } + + if (window->monitor) + [window->ns.object setLevel:NSMainMenuWindowLevel + 1]; + else + { + [(NSWindow*) window->ns.object center]; + _glfw.ns.cascadePoint = + NSPointToCGPoint([window->ns.object cascadeTopLeftFromPoint: + NSPointFromCGPoint(_glfw.ns.cascadePoint)]); + + if (wndconfig->resizable) + { + const NSWindowCollectionBehavior behavior = + NSWindowCollectionBehaviorFullScreenPrimary | + NSWindowCollectionBehaviorManaged; + [window->ns.object setCollectionBehavior:behavior]; + } + else + { + const NSWindowCollectionBehavior behavior = + NSWindowCollectionBehaviorFullScreenNone; + [window->ns.object setCollectionBehavior:behavior]; + } + + if (wndconfig->floating) + [window->ns.object setLevel:NSFloatingWindowLevel]; + + if (wndconfig->maximized) + [window->ns.object zoom:nil]; + } + + if (strlen(wndconfig->ns.frameName)) + [window->ns.object setFrameAutosaveName:@(wndconfig->ns.frameName)]; + + window->ns.view = [[GLFWContentView alloc] initWithGlfwWindow:window]; + window->ns.retina = wndconfig->ns.retina; + + if (fbconfig->transparent) + { + [window->ns.object setOpaque:NO]; + [window->ns.object setHasShadow:NO]; + [window->ns.object setBackgroundColor:[NSColor clearColor]]; + } + + [window->ns.object setContentView:window->ns.view]; + [window->ns.object makeFirstResponder:window->ns.view]; + [window->ns.object setTitle:@(wndconfig->title)]; + [window->ns.object setDelegate:window->ns.delegate]; + [window->ns.object setAcceptsMouseMovedEvents:YES]; + [window->ns.object setRestorable:NO]; + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 + if ([window->ns.object respondsToSelector:@selector(setTabbingMode:)]) + [window->ns.object setTabbingMode:NSWindowTabbingModeDisallowed]; +#endif + + _glfwPlatformGetWindowSize(window, &window->ns.width, &window->ns.height); + _glfwPlatformGetFramebufferSize(window, &window->ns.fbWidth, &window->ns.fbHeight); + + return GLFW_TRUE; +} + + +////////////////////////////////////////////////////////////////////////// +////// GLFW internal API ////// +////////////////////////////////////////////////////////////////////////// + +// Transforms a y-coordinate between the CG display and NS screen spaces +// +float _glfwTransformYNS(float y) +{ + return CGDisplayBounds(CGMainDisplayID()).size.height - y - 1; +} + + +////////////////////////////////////////////////////////////////////////// +////// GLFW platform API ////// +////////////////////////////////////////////////////////////////////////// + +int _glfwPlatformCreateWindow(_GLFWwindow* window, + const _GLFWwndconfig* wndconfig, + const _GLFWctxconfig* ctxconfig, + const _GLFWfbconfig* fbconfig) +{ + @autoreleasepool { + + if (!_glfw.ns.finishedLaunching) + [NSApp run]; + + if (!createNativeWindow(window, wndconfig, fbconfig)) + return GLFW_FALSE; + + if (ctxconfig->client != GLFW_NO_API) + { + if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API) + { + if (!_glfwInitNSGL()) + return GLFW_FALSE; + if (!_glfwCreateContextNSGL(window, ctxconfig, fbconfig)) + return GLFW_FALSE; + } + else if (ctxconfig->source == GLFW_EGL_CONTEXT_API) + { + // EGL implementation on macOS use CALayer* EGLNativeWindowType so we + // need to get the layer for EGL window surface creation. + [window->ns.view setWantsLayer:YES]; + window->ns.layer = [window->ns.view layer]; + + if (!_glfwInitEGL()) + return GLFW_FALSE; + if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig)) + return GLFW_FALSE; + } + else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API) + { + if (!_glfwInitOSMesa()) + return GLFW_FALSE; + if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig)) + return GLFW_FALSE; + } + + if (!_glfwRefreshContextAttribs(window, ctxconfig)) + return GLFW_FALSE; + } + + if (window->monitor) + { + _glfwPlatformShowWindow(window); + _glfwPlatformFocusWindow(window); + acquireMonitor(window); + + if (wndconfig->centerCursor) + _glfwCenterCursorInContentArea(window); + } + else + { + if (wndconfig->visible) + { + _glfwPlatformShowWindow(window); + if (wndconfig->focused) + _glfwPlatformFocusWindow(window); + } + } + + return GLFW_TRUE; + + } // autoreleasepool +} + +void _glfwPlatformDestroyWindow(_GLFWwindow* window) +{ + @autoreleasepool { + + if (_glfw.ns.disabledCursorWindow == window) + _glfw.ns.disabledCursorWindow = NULL; + + [window->ns.object orderOut:nil]; + + if (window->monitor) + releaseMonitor(window); + + if (window->context.destroy) + window->context.destroy(window); + + [window->ns.object setDelegate:nil]; + [window->ns.delegate release]; + window->ns.delegate = nil; + + [window->ns.view release]; + window->ns.view = nil; + + [window->ns.object close]; + window->ns.object = nil; + + // HACK: Allow Cocoa to catch up before returning + _glfwPlatformPollEvents(); + + } // autoreleasepool +} + +void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) +{ + @autoreleasepool { + NSString* string = @(title); + [window->ns.object setTitle:string]; + // HACK: Set the miniwindow title explicitly as setTitle: doesn't update it + // if the window lacks NSWindowStyleMaskTitled + [window->ns.object setMiniwindowTitle:string]; + } // autoreleasepool +} + +void _glfwPlatformSetWindowIcon(_GLFWwindow* window, + int count, const GLFWimage* images) +{ + // Regular windows do not have icons +} + +void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) +{ + @autoreleasepool { + + const NSRect contentRect = + [window->ns.object contentRectForFrameRect:[window->ns.object frame]]; + + if (xpos) + *xpos = contentRect.origin.x; + if (ypos) + *ypos = _glfwTransformYNS(contentRect.origin.y + contentRect.size.height - 1); + + } // autoreleasepool +} + +void _glfwPlatformSetWindowPos(_GLFWwindow* window, int x, int y) +{ + @autoreleasepool { + + const NSRect contentRect = [window->ns.view frame]; + const NSRect dummyRect = NSMakeRect(x, _glfwTransformYNS(y + contentRect.size.height - 1), 0, 0); + const NSRect frameRect = [window->ns.object frameRectForContentRect:dummyRect]; + [window->ns.object setFrameOrigin:frameRect.origin]; + + } // autoreleasepool +} + +void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) +{ + @autoreleasepool { + + const NSRect contentRect = [window->ns.view frame]; + + if (width) + *width = contentRect.size.width; + if (height) + *height = contentRect.size.height; + + } // autoreleasepool +} + +void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) +{ + @autoreleasepool { + + if (window->monitor) + { + if (window->monitor->window == window) + acquireMonitor(window); + } + else + { + NSRect contentRect = + [window->ns.object contentRectForFrameRect:[window->ns.object frame]]; + contentRect.origin.y += contentRect.size.height - height; + contentRect.size = NSMakeSize(width, height); + [window->ns.object setFrame:[window->ns.object frameRectForContentRect:contentRect] + display:YES]; + } + + } // autoreleasepool +} + +void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, + int minwidth, int minheight, + int maxwidth, int maxheight) +{ + @autoreleasepool { + + if (minwidth == GLFW_DONT_CARE || minheight == GLFW_DONT_CARE) + [window->ns.object setContentMinSize:NSMakeSize(0, 0)]; + else + [window->ns.object setContentMinSize:NSMakeSize(minwidth, minheight)]; + + if (maxwidth == GLFW_DONT_CARE || maxheight == GLFW_DONT_CARE) + [window->ns.object setContentMaxSize:NSMakeSize(DBL_MAX, DBL_MAX)]; + else + [window->ns.object setContentMaxSize:NSMakeSize(maxwidth, maxheight)]; + + } // autoreleasepool +} + +void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom) +{ + @autoreleasepool { + if (numer == GLFW_DONT_CARE || denom == GLFW_DONT_CARE) + [window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)]; + else + [window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)]; + } // autoreleasepool +} + +void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) +{ + @autoreleasepool { + + const NSRect contentRect = [window->ns.view frame]; + const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; + + if (width) + *width = (int) fbRect.size.width; + if (height) + *height = (int) fbRect.size.height; + + } // autoreleasepool +} + +void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, + int* left, int* top, + int* right, int* bottom) +{ + @autoreleasepool { + + const NSRect contentRect = [window->ns.view frame]; + const NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect]; + + if (left) + *left = contentRect.origin.x - frameRect.origin.x; + if (top) + *top = frameRect.origin.y + frameRect.size.height - + contentRect.origin.y - contentRect.size.height; + if (right) + *right = frameRect.origin.x + frameRect.size.width - + contentRect.origin.x - contentRect.size.width; + if (bottom) + *bottom = contentRect.origin.y - frameRect.origin.y; + + } // autoreleasepool +} + +void _glfwPlatformGetWindowContentScale(_GLFWwindow* window, + float* xscale, float* yscale) +{ + @autoreleasepool { + + const NSRect points = [window->ns.view frame]; + const NSRect pixels = [window->ns.view convertRectToBacking:points]; + + if (xscale) + *xscale = (float) (pixels.size.width / points.size.width); + if (yscale) + *yscale = (float) (pixels.size.height / points.size.height); + + } // autoreleasepool +} + +void _glfwPlatformIconifyWindow(_GLFWwindow* window) +{ + @autoreleasepool { + [window->ns.object miniaturize:nil]; + } // autoreleasepool +} + +void _glfwPlatformRestoreWindow(_GLFWwindow* window) +{ + @autoreleasepool { + if ([window->ns.object isMiniaturized]) + [window->ns.object deminiaturize:nil]; + else if ([window->ns.object isZoomed]) + [window->ns.object zoom:nil]; + } // autoreleasepool +} + +void _glfwPlatformMaximizeWindow(_GLFWwindow* window) +{ + @autoreleasepool { + if (![window->ns.object isZoomed]) + [window->ns.object zoom:nil]; + } // autoreleasepool +} + +void _glfwPlatformShowWindow(_GLFWwindow* window) +{ + @autoreleasepool { + [window->ns.object orderFront:nil]; + } // autoreleasepool +} + +void _glfwPlatformHideWindow(_GLFWwindow* window) +{ + @autoreleasepool { + [window->ns.object orderOut:nil]; + } // autoreleasepool +} + +void _glfwPlatformRequestWindowAttention(_GLFWwindow* window) +{ + @autoreleasepool { + [NSApp requestUserAttention:NSInformationalRequest]; + } // autoreleasepool +} + +void _glfwPlatformFocusWindow(_GLFWwindow* window) +{ + @autoreleasepool { + // Make us the active application + // HACK: This is here to prevent applications using only hidden windows from + // being activated, but should probably not be done every time any + // window is shown + [NSApp activateIgnoringOtherApps:YES]; + [window->ns.object makeKeyAndOrderFront:nil]; + } // autoreleasepool +} + +void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, + _GLFWmonitor* monitor, + int xpos, int ypos, + int width, int height, + int refreshRate) +{ + @autoreleasepool { + + if (window->monitor == monitor) + { + if (monitor) + { + if (monitor->window == window) + acquireMonitor(window); + } + else + { + const NSRect contentRect = + NSMakeRect(xpos, _glfwTransformYNS(ypos + height - 1), width, height); + const NSUInteger styleMask = [window->ns.object styleMask]; + const NSRect frameRect = + [window->ns.object frameRectForContentRect:contentRect + styleMask:styleMask]; + + [window->ns.object setFrame:frameRect display:YES]; + } + + return; + } + + if (window->monitor) + releaseMonitor(window); + + _glfwInputWindowMonitor(window, monitor); + + // HACK: Allow the state cached in Cocoa to catch up to reality + // TODO: Solve this in a less terrible way + _glfwPlatformPollEvents(); + + NSUInteger styleMask = [window->ns.object styleMask]; + + if (window->monitor) + { + styleMask &= ~(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable); + styleMask |= NSWindowStyleMaskBorderless; + } + else + { + if (window->decorated) + { + styleMask &= ~NSWindowStyleMaskBorderless; + styleMask |= (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable); + } + + if (window->resizable) + styleMask |= NSWindowStyleMaskResizable; + else + styleMask &= ~NSWindowStyleMaskResizable; + } + + [window->ns.object setStyleMask:styleMask]; + // HACK: Changing the style mask can cause the first responder to be cleared + [window->ns.object makeFirstResponder:window->ns.view]; + + if (window->monitor) + { + [window->ns.object setLevel:NSMainMenuWindowLevel + 1]; + [window->ns.object setHasShadow:NO]; + + acquireMonitor(window); + } + else + { + NSRect contentRect = NSMakeRect(xpos, _glfwTransformYNS(ypos + height - 1), + width, height); + NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect + styleMask:styleMask]; + [window->ns.object setFrame:frameRect display:YES]; + + if (window->numer != GLFW_DONT_CARE && + window->denom != GLFW_DONT_CARE) + { + [window->ns.object setContentAspectRatio:NSMakeSize(window->numer, + window->denom)]; + } + + if (window->minwidth != GLFW_DONT_CARE && + window->minheight != GLFW_DONT_CARE) + { + [window->ns.object setContentMinSize:NSMakeSize(window->minwidth, + window->minheight)]; + } + + if (window->maxwidth != GLFW_DONT_CARE && + window->maxheight != GLFW_DONT_CARE) + { + [window->ns.object setContentMaxSize:NSMakeSize(window->maxwidth, + window->maxheight)]; + } + + if (window->floating) + [window->ns.object setLevel:NSFloatingWindowLevel]; + else + [window->ns.object setLevel:NSNormalWindowLevel]; + + if (window->resizable) + { + const NSWindowCollectionBehavior behavior = + NSWindowCollectionBehaviorFullScreenPrimary | + NSWindowCollectionBehaviorManaged; + [window->ns.object setCollectionBehavior:behavior]; + } + else + { + const NSWindowCollectionBehavior behavior = + NSWindowCollectionBehaviorFullScreenNone; + [window->ns.object setCollectionBehavior:behavior]; + } + + [window->ns.object setHasShadow:YES]; + // HACK: Clearing NSWindowStyleMaskTitled resets and disables the window + // title property but the miniwindow title property is unaffected + [window->ns.object setTitle:[window->ns.object miniwindowTitle]]; + } + + } // autoreleasepool +} + +int _glfwPlatformWindowFocused(_GLFWwindow* window) +{ + @autoreleasepool { + return [window->ns.object isKeyWindow]; + } // autoreleasepool +} + +int _glfwPlatformWindowIconified(_GLFWwindow* window) +{ + @autoreleasepool { + return [window->ns.object isMiniaturized]; + } // autoreleasepool +} + +int _glfwPlatformWindowVisible(_GLFWwindow* window) +{ + @autoreleasepool { + return [window->ns.object isVisible]; + } // autoreleasepool +} + +int _glfwPlatformWindowMaximized(_GLFWwindow* window) +{ + @autoreleasepool { + + if (window->resizable) + return [window->ns.object isZoomed]; + else + return GLFW_FALSE; + + } // autoreleasepool +} + +int _glfwPlatformWindowHovered(_GLFWwindow* window) +{ + @autoreleasepool { + + const NSPoint point = [NSEvent mouseLocation]; + + if ([NSWindow windowNumberAtPoint:point belowWindowWithWindowNumber:0] != + [window->ns.object windowNumber]) + { + return GLFW_FALSE; + } + + return NSMouseInRect(point, + [window->ns.object convertRectToScreen:[window->ns.view frame]], NO); + + } // autoreleasepool +} + +int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) +{ + @autoreleasepool { + return ![window->ns.object isOpaque] && ![window->ns.view isOpaque]; + } // autoreleasepool +} + +void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled) +{ + @autoreleasepool { + + const NSUInteger styleMask = [window->ns.object styleMask]; + if (enabled) + { + [window->ns.object setStyleMask:(styleMask | NSWindowStyleMaskResizable)]; + const NSWindowCollectionBehavior behavior = + NSWindowCollectionBehaviorFullScreenPrimary | + NSWindowCollectionBehaviorManaged; + [window->ns.object setCollectionBehavior:behavior]; + } + else + { + [window->ns.object setStyleMask:(styleMask & ~NSWindowStyleMaskResizable)]; + const NSWindowCollectionBehavior behavior = + NSWindowCollectionBehaviorFullScreenNone; + [window->ns.object setCollectionBehavior:behavior]; + } + + } // autoreleasepool +} + +void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled) +{ + @autoreleasepool { + + NSUInteger styleMask = [window->ns.object styleMask]; + if (enabled) + { + styleMask |= (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable); + styleMask &= ~NSWindowStyleMaskBorderless; + } + else + { + styleMask |= NSWindowStyleMaskBorderless; + styleMask &= ~(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable); + } + + [window->ns.object setStyleMask:styleMask]; + [window->ns.object makeFirstResponder:window->ns.view]; + + } // autoreleasepool +} + +void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) +{ + @autoreleasepool { + if (enabled) + [window->ns.object setLevel:NSFloatingWindowLevel]; + else + [window->ns.object setLevel:NSNormalWindowLevel]; + } // autoreleasepool +} + +float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) +{ + @autoreleasepool { + return (float) [window->ns.object alphaValue]; + } // autoreleasepool +} + +void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) +{ + @autoreleasepool { + [window->ns.object setAlphaValue:opacity]; + } // autoreleasepool +} + +void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window, GLFWbool enabled) +{ +} + +GLFWbool _glfwPlatformRawMouseMotionSupported(void) +{ + return GLFW_FALSE; +} + +void _glfwPlatformPollEvents(void) +{ + @autoreleasepool { + + if (!_glfw.ns.finishedLaunching) + [NSApp run]; + + for (;;) + { + NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny + untilDate:[NSDate distantPast] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (event == nil) + break; + + [NSApp sendEvent:event]; + } + + } // autoreleasepool +} + +void _glfwPlatformWaitEvents(void) +{ + @autoreleasepool { + + if (!_glfw.ns.finishedLaunching) + [NSApp run]; + + // I wanted to pass NO to dequeue:, and rely on PollEvents to + // dequeue and send. For reasons not at all clear to me, passing + // NO to dequeue: causes this method never to return. + NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny + untilDate:[NSDate distantFuture] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + [NSApp sendEvent:event]; + + _glfwPlatformPollEvents(); + + } // autoreleasepool +} + +void _glfwPlatformWaitEventsTimeout(double timeout) +{ + @autoreleasepool { + + if (!_glfw.ns.finishedLaunching) + [NSApp run]; + + NSDate* date = [NSDate dateWithTimeIntervalSinceNow:timeout]; + NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny + untilDate:date + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (event) + [NSApp sendEvent:event]; + + _glfwPlatformPollEvents(); + + } // autoreleasepool +} + +void _glfwPlatformPostEmptyEvent(void) +{ + @autoreleasepool { + + if (!_glfw.ns.finishedLaunching) + [NSApp run]; + + NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined + location:NSMakePoint(0, 0) + modifierFlags:0 + timestamp:0 + windowNumber:0 + context:nil + subtype:0 + data1:0 + data2:0]; + [NSApp postEvent:event atStart:YES]; + + } // autoreleasepool +} + +void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) +{ + @autoreleasepool { + + const NSRect contentRect = [window->ns.view frame]; + // NOTE: The returned location uses base 0,1 not 0,0 + const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; + + if (xpos) + *xpos = pos.x; + if (ypos) + *ypos = contentRect.size.height - pos.y; + + } // autoreleasepool +} + +void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y) +{ + @autoreleasepool { + + updateCursorImage(window); + + const NSRect contentRect = [window->ns.view frame]; + // NOTE: The returned location uses base 0,1 not 0,0 + const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; + + window->ns.cursorWarpDeltaX += x - pos.x; + window->ns.cursorWarpDeltaY += y - contentRect.size.height + pos.y; + + if (window->monitor) + { + CGDisplayMoveCursorToPoint(window->monitor->ns.displayID, + CGPointMake(x, y)); + } + else + { + const NSRect localRect = NSMakeRect(x, contentRect.size.height - y - 1, 0, 0); + const NSRect globalRect = [window->ns.object convertRectToScreen:localRect]; + const NSPoint globalPoint = globalRect.origin; + + CGWarpMouseCursorPosition(CGPointMake(globalPoint.x, + _glfwTransformYNS(globalPoint.y))); + } + + // HACK: Calling this right after setting the cursor position prevents macOS + // from freezing the cursor for a fraction of a second afterwards + if (window->cursorMode != GLFW_CURSOR_DISABLED) + CGAssociateMouseAndMouseCursorPosition(true); + + } // autoreleasepool +} + +void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) +{ + @autoreleasepool { + if (_glfwPlatformWindowFocused(window)) + updateCursorMode(window); + } // autoreleasepool +} + +const char* _glfwPlatformGetScancodeName(int scancode) +{ + @autoreleasepool { + + if (scancode < 0 || scancode > 0xff || + _glfw.ns.keycodes[scancode] == GLFW_KEY_UNKNOWN) + { + _glfwInputError(GLFW_INVALID_VALUE, "Invalid scancode %i", scancode); + return NULL; + } + + const int key = _glfw.ns.keycodes[scancode]; + + UInt32 deadKeyState = 0; + UniChar characters[4]; + UniCharCount characterCount = 0; + + if (UCKeyTranslate([(NSData*) _glfw.ns.unicodeData bytes], + scancode, + kUCKeyActionDisplay, + 0, + LMGetKbdType(), + kUCKeyTranslateNoDeadKeysBit, + &deadKeyState, + sizeof(characters) / sizeof(characters[0]), + &characterCount, + characters) != noErr) + { + return NULL; + } + + if (!characterCount) + return NULL; + + CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, + characters, + characterCount, + kCFAllocatorNull); + CFStringGetCString(string, + _glfw.ns.keynames[key], + sizeof(_glfw.ns.keynames[key]), + kCFStringEncodingUTF8); + CFRelease(string); + + return _glfw.ns.keynames[key]; + + } // autoreleasepool +} + +int _glfwPlatformGetKeyScancode(int key) +{ + return _glfw.ns.scancodes[key]; +} + +int _glfwPlatformCreateCursor(_GLFWcursor* cursor, + const GLFWimage* image, + int xhot, int yhot) +{ + @autoreleasepool { + + NSImage* native; + NSBitmapImageRep* rep; + + rep = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:NULL + pixelsWide:image->width + pixelsHigh:image->height + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSCalibratedRGBColorSpace + bitmapFormat:NSBitmapFormatAlphaNonpremultiplied + bytesPerRow:image->width * 4 + bitsPerPixel:32]; + + if (rep == nil) + return GLFW_FALSE; + + memcpy([rep bitmapData], image->pixels, image->width * image->height * 4); + + native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)]; + [native addRepresentation:rep]; + + cursor->ns.object = [[NSCursor alloc] initWithImage:native + hotSpot:NSMakePoint(xhot, yhot)]; + + [native release]; + [rep release]; + + if (cursor->ns.object == nil) + return GLFW_FALSE; + + return GLFW_TRUE; + + } // autoreleasepool +} + +int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape) +{ + @autoreleasepool { + + if (shape == GLFW_ARROW_CURSOR) + cursor->ns.object = [NSCursor arrowCursor]; + else if (shape == GLFW_IBEAM_CURSOR) + cursor->ns.object = [NSCursor IBeamCursor]; + else if (shape == GLFW_CROSSHAIR_CURSOR) + cursor->ns.object = [NSCursor crosshairCursor]; + else if (shape == GLFW_HAND_CURSOR) + cursor->ns.object = [NSCursor pointingHandCursor]; + else if (shape == GLFW_HRESIZE_CURSOR) + cursor->ns.object = [NSCursor resizeLeftRightCursor]; + else if (shape == GLFW_VRESIZE_CURSOR) + cursor->ns.object = [NSCursor resizeUpDownCursor]; + + if (!cursor->ns.object) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Cocoa: Failed to retrieve standard cursor"); + return GLFW_FALSE; + } + + [cursor->ns.object retain]; + return GLFW_TRUE; + + } // autoreleasepool +} + +void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) +{ + @autoreleasepool { + if (cursor->ns.object) + [(NSCursor*) cursor->ns.object release]; + } // autoreleasepool +} + +void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) +{ + @autoreleasepool { + if (cursorInContentArea(window)) + updateCursorImage(window); + } // autoreleasepool +} + +void _glfwPlatformSetClipboardString(const char* string) +{ + @autoreleasepool { + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil]; + [pasteboard setString:@(string) forType:NSPasteboardTypeString]; + } // autoreleasepool +} + +const char* _glfwPlatformGetClipboardString(void) +{ + @autoreleasepool { + + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + + if (![[pasteboard types] containsObject:NSPasteboardTypeString]) + { + _glfwInputError(GLFW_FORMAT_UNAVAILABLE, + "Cocoa: Failed to retrieve string from pasteboard"); + return NULL; + } + + NSString* object = [pasteboard stringForType:NSPasteboardTypeString]; + if (!object) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Cocoa: Failed to retrieve object from pasteboard"); + return NULL; + } + + free(_glfw.ns.clipboardString); + _glfw.ns.clipboardString = _glfw_strdup([object UTF8String]); + + return _glfw.ns.clipboardString; + + } // autoreleasepool +} + +void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) +{ + if (_glfw.vk.KHR_surface && _glfw.vk.EXT_metal_surface) + { + extensions[0] = "VK_KHR_surface"; + extensions[1] = "VK_EXT_metal_surface"; + } + else if (_glfw.vk.KHR_surface && _glfw.vk.MVK_macos_surface) + { + extensions[0] = "VK_KHR_surface"; + extensions[1] = "VK_MVK_macos_surface"; + } +} + +int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance, + VkPhysicalDevice device, + uint32_t queuefamily) +{ + return GLFW_TRUE; +} + +VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, + _GLFWwindow* window, + const VkAllocationCallbacks* allocator, + VkSurfaceKHR* surface) +{ + @autoreleasepool { + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101100 + // HACK: Dynamically load Core Animation to avoid adding an extra + // dependency for the majority who don't use MoltenVK + NSBundle* bundle = [NSBundle bundleWithPath:@"/System/Library/Frameworks/QuartzCore.framework"]; + if (!bundle) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Cocoa: Failed to find QuartzCore.framework"); + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + + // NOTE: Create the layer here as makeBackingLayer should not return nil + window->ns.layer = [[bundle classNamed:@"CAMetalLayer"] layer]; + if (!window->ns.layer) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Cocoa: Failed to create layer for view"); + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + + if (window->ns.retina) + [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]]; + + [window->ns.view setLayer:window->ns.layer]; + [window->ns.view setWantsLayer:YES]; + + VkResult err; + + if (_glfw.vk.EXT_metal_surface) + { + VkMetalSurfaceCreateInfoEXT sci; + + PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT; + vkCreateMetalSurfaceEXT = (PFN_vkCreateMetalSurfaceEXT) + vkGetInstanceProcAddr(instance, "vkCreateMetalSurfaceEXT"); + if (!vkCreateMetalSurfaceEXT) + { + _glfwInputError(GLFW_API_UNAVAILABLE, + "Cocoa: Vulkan instance missing VK_EXT_metal_surface extension"); + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + + memset(&sci, 0, sizeof(sci)); + sci.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; + sci.pLayer = window->ns.layer; + + err = vkCreateMetalSurfaceEXT(instance, &sci, allocator, surface); + } + else + { + VkMacOSSurfaceCreateInfoMVK sci; + + PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK; + vkCreateMacOSSurfaceMVK = (PFN_vkCreateMacOSSurfaceMVK) + vkGetInstanceProcAddr(instance, "vkCreateMacOSSurfaceMVK"); + if (!vkCreateMacOSSurfaceMVK) + { + _glfwInputError(GLFW_API_UNAVAILABLE, + "Cocoa: Vulkan instance missing VK_MVK_macos_surface extension"); + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + + memset(&sci, 0, sizeof(sci)); + sci.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; + sci.pView = window->ns.view; + + err = vkCreateMacOSSurfaceMVK(instance, &sci, allocator, surface); + } + + if (err) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Cocoa: Failed to create Vulkan surface: %s", + _glfwGetVulkanResultString(err)); + } + + return err; +#else + return VK_ERROR_EXTENSION_NOT_PRESENT; +#endif + + } // autoreleasepool +} + + +////////////////////////////////////////////////////////////////////////// +////// GLFW native API ////// +////////////////////////////////////////////////////////////////////////// + +GLFWAPI id glfwGetCocoaWindow(GLFWwindow* handle) +{ + _GLFWwindow* window = (_GLFWwindow*) handle; + _GLFW_REQUIRE_INIT_OR_RETURN(nil); + return window->ns.object; +} + |