mirror of
https://github.com/glfw/glfw.git
synced 2024-11-22 21:14:35 +00:00
e50ee39eac
The reasoning here is that glfwRestoreWindow will change nothing for
a windowed non-resizable window on Cocoa, and silently refusing to
maximize seems slightly more like something other platforms would do.
This is possibly either the right thing to do or the wrong one.
(cherry picked from commit 6f8ec4075b
)
1907 lines
54 KiB
Objective-C
1907 lines
54 KiB
Objective-C
//========================================================================
|
|
// 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 the style mask corresponding to the window settings
|
|
//
|
|
static NSUInteger getStyleMask(_GLFWwindow* window)
|
|
{
|
|
NSUInteger styleMask = NSWindowStyleMaskMiniaturizable;
|
|
|
|
if (window->monitor || !window->decorated)
|
|
styleMask |= NSWindowStyleMaskBorderless;
|
|
else
|
|
{
|
|
styleMask |= NSWindowStyleMaskTitled |
|
|
NSWindowStyleMaskClosable;
|
|
|
|
if (window->resizable)
|
|
styleMask |= NSWindowStyleMaskResizable;
|
|
}
|
|
|
|
return styleMask;
|
|
}
|
|
|
|
// 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);
|
|
|
|
window->ns.object = [[GLFWWindow alloc]
|
|
initWithContentRect:contentRect
|
|
styleMask:getStyleMask(window)
|
|
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 NSRect frameRect =
|
|
[window->ns.object frameRectForContentRect:contentRect
|
|
styleMask:getStyleMask(window)];
|
|
|
|
[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();
|
|
|
|
const NSUInteger styleMask = getStyleMask(window);
|
|
[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 {
|
|
|
|
[window->ns.object setStyleMask:getStyleMask(window)];
|
|
|
|
if (enabled)
|
|
{
|
|
const NSWindowCollectionBehavior behavior =
|
|
NSWindowCollectionBehaviorFullScreenPrimary |
|
|
NSWindowCollectionBehaviorManaged;
|
|
[window->ns.object setCollectionBehavior:behavior];
|
|
}
|
|
else
|
|
{
|
|
const NSWindowCollectionBehavior behavior =
|
|
NSWindowCollectionBehaviorFullScreenNone;
|
|
[window->ns.object setCollectionBehavior:behavior];
|
|
}
|
|
|
|
} // autoreleasepool
|
|
}
|
|
|
|
void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled)
|
|
{
|
|
@autoreleasepool {
|
|
[window->ns.object setStyleMask:getStyleMask(window)];
|
|
[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;
|
|
}
|
|
|