cocoa_window.m 51.1 KB
Newer Older
Camilla Berglund's avatar
Camilla Berglund committed
1
//========================================================================
2
// GLFW 3.3 macOS - www.glfw.org
Camilla Berglund's avatar
Camilla Berglund committed
3
//------------------------------------------------------------------------
Camilla Löwy's avatar
Camilla Löwy committed
4
// Copyright (c) 2009-2019 Camilla Löwy <elmindreda@glfw.org>
Camilla Berglund's avatar
Camilla Berglund committed
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//
// 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.
//
//========================================================================
26
27
// It is fine to use C99 in this file because it will not be built with VS
//========================================================================
Camilla Berglund's avatar
Camilla Berglund committed
28
29
30

#include "internal.h"

31
#include <float.h>
32
33
#include <string.h>

Camilla Berglund's avatar
Camilla Berglund committed
34
35
36
37
// Returns the style mask corresponding to the window settings
//
static NSUInteger getStyleMask(_GLFWwindow* window)
{
38
    NSUInteger styleMask = NSWindowStyleMaskMiniaturizable;
Camilla Berglund's avatar
Camilla Berglund committed
39
40

    if (window->monitor || !window->decorated)
41
        styleMask |= NSWindowStyleMaskBorderless;
Camilla Berglund's avatar
Camilla Berglund committed
42
43
    else
    {
44
        styleMask |= NSWindowStyleMaskTitled |
45
                     NSWindowStyleMaskClosable;
Camilla Berglund's avatar
Camilla Berglund committed
46
47

        if (window->resizable)
48
            styleMask |= NSWindowStyleMaskResizable;
Camilla Berglund's avatar
Camilla Berglund committed
49
50
51
52
53
    }

    return styleMask;
}

Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
54
// Returns whether the cursor is in the content area of the specified window
55
//
Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
56
static GLFWbool cursorInContentArea(_GLFWwindow* window)
57
{
58
59
60
61
    const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream];
    return [window->ns.view mouse:pos inRect:[window->ns.view frame]];
}

Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
62
// Hides the cursor if not already hidden
63
//
Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
64
static void hideCursor(_GLFWwindow* window)
65
{
Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
66
67
    if (!_glfw.ns.cursorHidden)
    {
68
        [NSCursor hide];
Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
69
70
71
        _glfw.ns.cursorHidden = GLFW_TRUE;
    }
}
72

Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
73
74
75
76
77
78
79
80
81
// Shows the cursor if not already shown
//
static void showCursor(_GLFWwindow* window)
{
    if (_glfw.ns.cursorHidden)
    {
        [NSCursor unhide];
        _glfw.ns.cursorHidden = GLFW_FALSE;
    }
82
83
}

84
85
86
87
88
// Updates the cursor image according to its cursor mode
//
static void updateCursorImage(_GLFWwindow* window)
{
    if (window->cursorMode == GLFW_CURSOR_NORMAL)
89
    {
Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
90
        showCursor(window);
91

92
93
94
95
96
97
        if (window->cursor)
            [(NSCursor*) window->cursor->ns.object set];
        else
            [[NSCursor arrowCursor] set];
    }
    else
Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
98
        hideCursor(window);
99
100
}

101
102
103
104
105
106
107
108
109
110
// 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);
Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
111
        _glfwCenterCursorInContentArea(window);
112
113
114
115
116
117
118
119
120
121
122
        CGAssociateMouseAndMouseCursorPosition(false);
    }
    else if (_glfw.ns.disabledCursorWindow == window)
    {
        _glfw.ns.disabledCursorWindow = NULL;
        CGAssociateMouseAndMouseCursorPosition(true);
        _glfwPlatformSetCursorPos(window,
                                  _glfw.ns.restoreCursorPosX,
                                  _glfw.ns.restoreCursorPosY);
    }

Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
123
    if (cursorInContentArea(window))
124
125
126
        updateCursorImage(window);
}

127
// Make the specified window and its video mode active on its monitor
128
//
Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
129
static void acquireMonitor(_GLFWwindow* window)
130
{
Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
131
    _glfwSetVideoModeNS(window->monitor, &window->videoMode);
132
133
    const CGRect bounds = CGDisplayBounds(window->monitor->ns.displayID);
    const NSRect frame = NSMakeRect(bounds.origin.x,
134
                                    _glfwTransformYNS(bounds.origin.y + bounds.size.height - 1),
135
136
137
138
                                    bounds.size.width,
                                    bounds.size.height);

    [window->ns.object setFrame:frame display:YES];
139

Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
140
    _glfwInputMonitorWindow(window->monitor, window);
141
142
}

143
// Remove the window and restore the original video mode
144
//
145
static void releaseMonitor(_GLFWwindow* window)
146
{
147
148
149
    if (window->monitor->window != window)
        return;

Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
150
    _glfwInputMonitorWindow(window->monitor, NULL);
151
    _glfwRestoreVideoModeNS(window->monitor);
152
153
}

154
// Translates macOS key modifiers into GLFW ones
155
156
157
158
159
//
static int translateFlags(NSUInteger flags)
{
    int mods = 0;

160
    if (flags & NSEventModifierFlagShift)
161
        mods |= GLFW_MOD_SHIFT;
162
    if (flags & NSEventModifierFlagControl)
163
        mods |= GLFW_MOD_CONTROL;
164
    if (flags & NSEventModifierFlagOption)
165
        mods |= GLFW_MOD_ALT;
166
    if (flags & NSEventModifierFlagCommand)
167
        mods |= GLFW_MOD_SUPER;
168
169
    if (flags & NSEventModifierFlagCapsLock)
        mods |= GLFW_MOD_CAPS_LOCK;
170
171
172
173

    return mods;
}

174
// Translates a macOS keycode to a GLFW keycode
175
176
177
//
static int translateKey(unsigned int key)
{
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
178
    if (key >= sizeof(_glfw.ns.keycodes) / sizeof(_glfw.ns.keycodes[0]))
179
180
        return GLFW_KEY_UNKNOWN;

Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
181
    return _glfw.ns.keycodes[key];
182
183
}

Xo Wang's avatar
Xo Wang committed
184
185
186
187
188
189
190
191
// 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:
192
            return NSEventModifierFlagShift;
Xo Wang's avatar
Xo Wang committed
193
194
        case GLFW_KEY_LEFT_CONTROL:
        case GLFW_KEY_RIGHT_CONTROL:
195
            return NSEventModifierFlagControl;
Xo Wang's avatar
Xo Wang committed
196
197
        case GLFW_KEY_LEFT_ALT:
        case GLFW_KEY_RIGHT_ALT:
198
            return NSEventModifierFlagOption;
Xo Wang's avatar
Xo Wang committed
199
200
        case GLFW_KEY_LEFT_SUPER:
        case GLFW_KEY_RIGHT_SUPER:
201
            return NSEventModifierFlagCommand;
202
203
        case GLFW_KEY_CAPS_LOCK:
            return NSEventModifierFlagCapsLock;
Xo Wang's avatar
Xo Wang committed
204
205
206
207
208
    }

    return 0;
}

209
210
// Defines a constant for empty ranges in NSTextInputClient
//
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
211
static const NSRange kEmptyRange = { NSNotFound, 0 };
212

213

214
//------------------------------------------------------------------------
Camilla Berglund's avatar
Camilla Berglund committed
215
// Delegate for window related notifications
216
//------------------------------------------------------------------------
Camilla Berglund's avatar
Camilla Berglund committed
217
218

@interface GLFWWindowDelegate : NSObject
219
220
221
222
{
    _GLFWwindow* window;
}

223
- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow;
224

Camilla Berglund's avatar
Camilla Berglund committed
225
226
227
228
@end

@implementation GLFWWindowDelegate

229
- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow
230
231
232
233
234
235
236
237
238
{
    self = [super init];
    if (self != nil)
        window = initWindow;

    return self;
}

- (BOOL)windowShouldClose:(id)sender
Camilla Berglund's avatar
Camilla Berglund committed
239
{
240
    _glfwInputWindowCloseRequest(window);
Camilla Berglund's avatar
Camilla Berglund committed
241
242
243
244
245
    return NO;
}

- (void)windowDidResize:(NSNotification *)notification
{
246
    if (window->context.client != GLFW_NO_API)
247
        [window->context.nsgl.object update];
Camilla Berglund's avatar
Camilla Berglund committed
248

249
    if (_glfw.ns.disabledCursorWindow == window)
Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
250
        _glfwCenterCursorInContentArea(window);
251

252
253
254
255
256
257
258
    const int maximized = [window->ns.object isZoomed];
    if (window->ns.maximized != maximized)
    {
        window->ns.maximized = maximized;
        _glfwInputWindowMaximize(window, maximized);
    }

259
    const NSRect contentRect = [window->ns.view frame];
260
    const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect];
261

262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
    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);
    }
Camilla Berglund's avatar
Camilla Berglund committed
277
278
}

279
280
- (void)windowDidMove:(NSNotification *)notification
{
281
    if (window->context.client != GLFW_NO_API)
282
        [window->context.nsgl.object update];
Camilla Berglund's avatar
Camilla Berglund committed
283

284
    if (_glfw.ns.disabledCursorWindow == window)
Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
285
        _glfwCenterCursorInContentArea(window);
286

287
288
289
    int x, y;
    _glfwPlatformGetWindowPos(window, &x, &y);
    _glfwInputWindowPos(window, x, y);
290
291
}

Camilla Berglund's avatar
Camilla Berglund committed
292
- (void)windowDidMiniaturize:(NSNotification *)notification
293
{
294
    if (window->monitor)
295
        releaseMonitor(window);
296

297
    _glfwInputWindowIconify(window, GLFW_TRUE);
298
299
}

Camilla Berglund's avatar
Camilla Berglund committed
300
- (void)windowDidDeminiaturize:(NSNotification *)notification
301
{
302
    if (window->monitor)
303
        acquireMonitor(window);
304

305
    _glfwInputWindowIconify(window, GLFW_FALSE);
306
307
}

Camilla Berglund's avatar
Camilla Berglund committed
308
- (void)windowDidBecomeKey:(NSNotification *)notification
309
{
310
    if (_glfw.ns.disabledCursorWindow == window)
Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
311
        _glfwCenterCursorInContentArea(window);
312

313
    _glfwInputWindowFocus(window, GLFW_TRUE);
314
    updateCursorMode(window);
315
316
}

Camilla Berglund's avatar
Camilla Berglund committed
317
- (void)windowDidResignKey:(NSNotification *)notification
318
{
319
    if (window->monitor && window->autoIconify)
320
        _glfwPlatformIconifyWindow(window);
321

322
    _glfwInputWindowFocus(window, GLFW_FALSE);
323
324
325
326
}

@end

Camilla Berglund's avatar
Camilla Berglund committed
327

328
//------------------------------------------------------------------------
Camilla Berglund's avatar
Camilla Berglund committed
329
// Content view class for the GLFW window
330
//------------------------------------------------------------------------
Camilla Berglund's avatar
Camilla Berglund committed
331

332
@interface GLFWContentView : NSView <NSTextInputClient>
333
334
{
    _GLFWwindow* window;
335
    NSTrackingArea* trackingArea;
336
    NSMutableAttributedString* markedText;
337
338
}

339
- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow;
340

Camilla Berglund's avatar
Camilla Berglund committed
341
342
343
344
@end

@implementation GLFWContentView

345
- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow
346
{
Camilla Berglund's avatar
Camilla Berglund committed
347
    self = [super init];
348
    if (self != nil)
349
    {
350
        window = initWindow;
351
        trackingArea = nil;
352
        markedText = [[NSMutableAttributedString alloc] init];
Camilla Berglund's avatar
Camilla Berglund committed
353

354
        [self updateTrackingAreas];
355
356
357
        // NOTE: kUTTypeURL corresponds to NSPasteboardTypeURL but is available
        //       on 10.7 without having been deprecated yet
        [self registerForDraggedTypes:@[(__bridge NSString*) kUTTypeURL]];
358
    }
359
360
361
362

    return self;
}

363
- (void)dealloc
364
365
{
    [trackingArea release];
366
    [markedText release];
367
368
369
    [super dealloc];
}

Camilla Berglund's avatar
Camilla Berglund committed
370
371
- (BOOL)isOpaque
{
372
    return [window->ns.object isOpaque];
Camilla Berglund's avatar
Camilla Berglund committed
373
374
375
376
377
378
379
380
381
382
383
384
}

- (BOOL)canBecomeKeyView
{
    return YES;
}

- (BOOL)acceptsFirstResponder
{
    return YES;
}

385
386
387
388
389
- (BOOL)wantsUpdateLayer
{
    return YES;
}

390
391
392
393
394
395
396
397
- (void)updateLayer
{
    if (window->context.client != GLFW_NO_API)
        [window->context.nsgl.object update];

    _glfwInputWindowDamage(window);
}

398
399
- (void)cursorUpdate:(NSEvent *)event
{
400
    updateCursorImage(window);
401
402
}

403
404
405
406
407
- (BOOL)acceptsFirstMouse:(NSEvent *)event
{
    return YES;
}

Camilla Berglund's avatar
Camilla Berglund committed
408
409
- (void)mouseDown:(NSEvent *)event
{
410
411
412
    _glfwInputMouseClick(window,
                         GLFW_MOUSE_BUTTON_LEFT,
                         GLFW_PRESS,
413
                         translateFlags([event modifierFlags]));
Camilla Berglund's avatar
Camilla Berglund committed
414
415
416
417
418
419
420
421
422
}

- (void)mouseDragged:(NSEvent *)event
{
    [self mouseMoved:event];
}

- (void)mouseUp:(NSEvent *)event
{
423
424
425
    _glfwInputMouseClick(window,
                         GLFW_MOUSE_BUTTON_LEFT,
                         GLFW_RELEASE,
426
                         translateFlags([event modifierFlags]));
Camilla Berglund's avatar
Camilla Berglund committed
427
428
429
430
}

- (void)mouseMoved:(NSEvent *)event
{
431
    if (window->cursorMode == GLFW_CURSOR_DISABLED)
432
    {
433
434
435
436
437
438
        const double dx = [event deltaX] - window->ns.cursorWarpDeltaX;
        const double dy = [event deltaY] - window->ns.cursorWarpDeltaY;

        _glfwInputCursorPos(window,
                            window->virtualCursorPosX + dx,
                            window->virtualCursorPosY + dy);
439
    }
Camilla Berglund's avatar
Camilla Berglund committed
440
441
    else
    {
Camilla Berglund's avatar
Camilla Berglund committed
442
        const NSRect contentRect = [window->ns.view frame];
443
        // NOTE: The returned location uses base 0,1 not 0,0
444
        const NSPoint pos = [event locationInWindow];
Camilla Berglund's avatar
Camilla Berglund committed
445

446
        _glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y);
Camilla Berglund's avatar
Camilla Berglund committed
447
    }
448

449
450
    window->ns.cursorWarpDeltaX = 0;
    window->ns.cursorWarpDeltaY = 0;
Camilla Berglund's avatar
Camilla Berglund committed
451
452
453
454
}

- (void)rightMouseDown:(NSEvent *)event
{
455
456
457
    _glfwInputMouseClick(window,
                         GLFW_MOUSE_BUTTON_RIGHT,
                         GLFW_PRESS,
458
                         translateFlags([event modifierFlags]));
Camilla Berglund's avatar
Camilla Berglund committed
459
460
461
462
463
464
465
466
467
}

- (void)rightMouseDragged:(NSEvent *)event
{
    [self mouseMoved:event];
}

- (void)rightMouseUp:(NSEvent *)event
{
468
469
470
    _glfwInputMouseClick(window,
                         GLFW_MOUSE_BUTTON_RIGHT,
                         GLFW_RELEASE,
471
                         translateFlags([event modifierFlags]));
Camilla Berglund's avatar
Camilla Berglund committed
472
473
474
475
}

- (void)otherMouseDown:(NSEvent *)event
{
476
    _glfwInputMouseClick(window,
477
                         (int) [event buttonNumber],
478
                         GLFW_PRESS,
479
                         translateFlags([event modifierFlags]));
Camilla Berglund's avatar
Camilla Berglund committed
480
481
482
483
484
485
486
487
488
}

- (void)otherMouseDragged:(NSEvent *)event
{
    [self mouseMoved:event];
}

- (void)otherMouseUp:(NSEvent *)event
{
489
    _glfwInputMouseClick(window,
490
                         (int) [event buttonNumber],
491
                         GLFW_RELEASE,
492
                         translateFlags([event modifierFlags]));
Camilla Berglund's avatar
Camilla Berglund committed
493
494
}

495
496
- (void)mouseExited:(NSEvent *)event
{
497
    if (window->cursorMode == GLFW_CURSOR_HIDDEN)
Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
498
        showCursor(window);
499

500
    _glfwInputCursorEnter(window, GLFW_FALSE);
501
502
503
504
}

- (void)mouseEntered:(NSEvent *)event
{
505
    if (window->cursorMode == GLFW_CURSOR_HIDDEN)
Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
506
        hideCursor(window);
507

508
    _glfwInputCursorEnter(window, GLFW_TRUE);
509
510
}

511
512
513
- (void)viewDidChangeBackingProperties
{
    const NSRect contentRect = [window->ns.view frame];
514
    const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect];
515

516
517
518
519
520
521
522
    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);
    }
523
524
525
526
527
528
529
530
531

    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)
    {
        window->ns.xscale = xscale;
        window->ns.yscale = yscale;
        _glfwInputWindowContentScale(window, xscale, yscale);
532

533
        if (window->ns.retina && window->ns.layer)
534
            [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]];
535
    }
536
537
538
539
}

- (void)drawRect:(NSRect)rect
{
540
    _glfwInputWindowDamage(window);
541
542
}

543
544
545
546
547
548
549
550
- (void)updateTrackingAreas
{
    if (trackingArea != nil)
    {
        [self removeTrackingArea:trackingArea];
        [trackingArea release];
    }

551
552
    const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
                                          NSTrackingActiveInKeyWindow |
553
                                          NSTrackingEnabledDuringMouseDrag |
554
555
556
                                          NSTrackingCursorUpdate |
                                          NSTrackingInVisibleRect |
                                          NSTrackingAssumeInside;
557
558
559
560
561
562
563

    trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds]
                                                options:options
                                                  owner:self
                                               userInfo:nil];

    [self addTrackingArea:trackingArea];
564
    [super updateTrackingAreas];
565
566
}

Camilla Berglund's avatar
Camilla Berglund committed
567
568
- (void)keyDown:(NSEvent *)event
{
569
570
    const int key = translateKey([event keyCode]);
    const int mods = translateFlags([event modifierFlags]);
571

572
    _glfwInputKey(window, key, [event keyCode], GLFW_PRESS, mods);
573

574
    [self interpretKeyEvents:@[event]];
Camilla Berglund's avatar
Camilla Berglund committed
575
576
577
578
}

- (void)flagsChanged:(NSEvent *)event
{
579
    int action;
580
    const unsigned int modifierFlags =
581
        [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
582
583
    const int key = translateKey([event keyCode]);
    const int mods = translateFlags(modifierFlags);
Xo Wang's avatar
Xo Wang committed
584
    const NSUInteger keyFlag = translateKeyToModifierFlag(key);
Camilla Berglund's avatar
Camilla Berglund committed
585

Xo Wang's avatar
Xo Wang committed
586
    if (keyFlag & modifierFlags)
587
    {
588
        if (window->keys[key] == GLFW_PRESS)
589
590
591
592
            action = GLFW_RELEASE;
        else
            action = GLFW_PRESS;
    }
Camilla Berglund's avatar
Camilla Berglund committed
593
    else
Camilla Berglund's avatar
Camilla Berglund committed
594
        action = GLFW_RELEASE;
Camilla Berglund's avatar
Camilla Berglund committed
595

596
    _glfwInputKey(window, key, [event keyCode], action, mods);
Camilla Berglund's avatar
Camilla Berglund committed
597
598
599
600
}

- (void)keyUp:(NSEvent *)event
{
601
602
603
    const int key = translateKey([event keyCode]);
    const int mods = translateFlags([event modifierFlags]);
    _glfwInputKey(window, key, [event keyCode], GLFW_RELEASE, mods);
Camilla Berglund's avatar
Camilla Berglund committed
604
605
606
607
}

- (void)scrollWheel:(NSEvent *)event
{
608
609
    double deltaX = [event scrollingDeltaX];
    double deltaY = [event scrollingDeltaY];
610

611
    if ([event hasPreciseScrollingDeltas])
612
    {
613
614
        deltaX *= 0.1;
        deltaY *= 0.1;
615
    }
616
617

    if (fabs(deltaX) > 0.0 || fabs(deltaY) > 0.0)
618
        _glfwInputScroll(window, deltaX, deltaY);
Camilla Berglund's avatar
Camilla Berglund committed
619
620
}

arturo's avatar
arturo committed
621
622
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
623
624
625
    // 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;
arturo's avatar
arturo committed
626
627
}

Camilla Berglund's avatar
Camilla Berglund committed
628
629
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
630
    const NSRect contentRect = [window->ns.view frame];
631
632
633
    // 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);
Camilla Berglund's avatar
Camilla Berglund committed
634

635
636
637
638
639
    NSPasteboard* pasteboard = [sender draggingPasteboard];
    NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES};
    NSArray* urls = [pasteboard readObjectsForClasses:@[[NSURL class]]
                                              options:options];
    const NSUInteger count = [urls count];
640
    if (count)
Camilla Berglund's avatar
Camilla Berglund committed
641
    {
642
        char** paths = calloc(count, sizeof(char*));
Camilla Berglund's avatar
Camilla Berglund committed
643

644
        for (NSUInteger i = 0;  i < count;  i++)
645
            paths[i] = _glfw_strdup([urls[i] fileSystemRepresentation]);
Camilla Berglund's avatar
Camilla Berglund committed
646

Stephen Gowen's avatar
Stephen Gowen committed
647
        _glfwInputDrop(window, (int) count, (const char**) paths);
Camilla Berglund's avatar
Camilla Berglund committed
648

649
        for (NSUInteger i = 0;  i < count;  i++)
650
651
            free(paths[i]);
        free(paths);
Camilla Berglund's avatar
Camilla Berglund committed
652
653
654
    }

    return YES;
arturo's avatar
arturo committed
655
656
}

657
658
- (BOOL)hasMarkedText
{
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
659
    return [markedText length] > 0;
660
661
662
663
}

- (NSRange)markedRange
{
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
664
665
666
667
    if ([markedText length] > 0)
        return NSMakeRange(0, [markedText length] - 1);
    else
        return kEmptyRange;
668
669
670
671
672
673
674
}

- (NSRange)selectedRange
{
    return kEmptyRange;
}

Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
675
- (void)setMarkedText:(id)string
676
677
678
        selectedRange:(NSRange)selectedRange
     replacementRange:(NSRange)replacementRange
{
679
    [markedText release];
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
680
    if ([string isKindOfClass:[NSAttributedString class]])
681
        markedText = [[NSMutableAttributedString alloc] initWithAttributedString:string];
682
    else
683
        markedText = [[NSMutableAttributedString alloc] initWithString:string];
684
685
686
687
688
689
690
691
692
693
694
695
}

- (void)unmarkText
{
    [[markedText mutableString] setString:@""];
}

- (NSArray*)validAttributesForMarkedText
{
    return [NSArray array];
}

Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
696
- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
697
698
699
700
701
                                               actualRange:(NSRangePointer)actualRange
{
    return nil;
}

Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
702
- (NSUInteger)characterIndexForPoint:(NSPoint)point
703
704
705
706
{
    return 0;
}

Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
707
- (NSRect)firstRectForCharacterRange:(NSRange)range
708
709
                         actualRange:(NSRangePointer)actualRange
{
710
711
    const NSRect frame = [window->ns.view frame];
    return NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0);
712
713
}

Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
714
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
715
{
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
716
    NSString* characters;
717
718
    NSEvent* event = [NSApp currentEvent];
    const int mods = translateFlags([event modifierFlags]);
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
719
    const int plain = !(mods & GLFW_MOD_SUPER);
720

Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
721
722
723
724
    if ([string isKindOfClass:[NSAttributedString class]])
        characters = [string string];
    else
        characters = (NSString*) string;
725

726
727
    const NSUInteger length = [characters length];
    for (NSUInteger i = 0;  i < length;  i++)
728
729
730
731
732
733
734
735
736
    {
        const unichar codepoint = [characters characterAtIndex:i];
        if ((codepoint & 0xff00) == 0xf700)
            continue;

        _glfwInputChar(window, codepoint, mods, plain);
    }
}

737
738
739
740
- (void)doCommandBySelector:(SEL)selector
{
}

Camilla Berglund's avatar
Camilla Berglund committed
741
742
@end

Camilla Berglund's avatar
Camilla Berglund committed
743

Camilla Berglund's avatar
Camilla Berglund committed
744
745
746
747
748
749
750
751
752
753
754
//------------------------------------------------------------------------
// GLFW window class
//------------------------------------------------------------------------

@interface GLFWWindow : NSWindow {}
@end

@implementation GLFWWindow

- (BOOL)canBecomeKeyWindow
{
755
    // Required for NSWindowStyleMaskBorderless windows
Camilla Berglund's avatar
Camilla Berglund committed
756
757
758
    return YES;
}

759
760
761
762
763
- (BOOL)canBecomeMainWindow
{
    return YES;
}

Camilla Berglund's avatar
Camilla Berglund committed
764
765
766
@end


767
// Create the Cocoa window
768
//
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
769
static GLFWbool createNativeWindow(_GLFWwindow* window,
770
771
                                   const _GLFWwndconfig* wndconfig,
                                   const _GLFWfbconfig* fbconfig)
772
{
773
774
775
776
777
    window->ns.delegate = [[GLFWWindowDelegate alloc] initWithGlfwWindow:window];
    if (window->ns.delegate == nil)
    {
        _glfwInputError(GLFW_PLATFORM_ERROR,
                        "Cocoa: Failed to create window delegate");
778
        return GLFW_FALSE;
779
780
    }

781
782
    NSRect contentRect;

783
    if (window->monitor)
784
785
786
787
788
789
790
791
792
    {
        GLFWvidmode mode;
        int xpos, ypos;

        _glfwPlatformGetVideoMode(window->monitor, &mode);
        _glfwPlatformGetMonitorPos(window->monitor, &xpos, &ypos);

        contentRect = NSMakeRect(xpos, ypos, mode.width, mode.height);
    }
793
794
795
    else
        contentRect = NSMakeRect(0, 0, wndconfig->width, wndconfig->height);

796
    window->ns.object = [[GLFWWindow alloc]
797
        initWithContentRect:contentRect
Camilla Berglund's avatar
Camilla Berglund committed
798
                  styleMask:getStyleMask(window)
799
800
801
                    backing:NSBackingStoreBuffered
                      defer:NO];

802
    if (window->ns.object == nil)
803
    {
804
        _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create window");
805
        return GLFW_FALSE;
806
807
    }

808
    if (window->monitor)
809
810
811
        [window->ns.object setLevel:NSMainMenuWindowLevel + 1];
    else
    {
812
        [(NSWindow*) window->ns.object center];
813
814
815
        _glfw.ns.cascadePoint =
            NSPointToCGPoint([window->ns.object cascadeTopLeftFromPoint:
                              NSPointFromCGPoint(_glfw.ns.cascadePoint)]);
816

Camilla Berglund's avatar
Camilla Berglund committed
817
        if (wndconfig->resizable)
818
819
820
821
822
823
        {
            const NSWindowCollectionBehavior behavior =
                NSWindowCollectionBehaviorFullScreenPrimary |
                NSWindowCollectionBehaviorManaged;
            [window->ns.object setCollectionBehavior:behavior];
        }
Camilla Berglund's avatar
Camilla Berglund committed
824

825
826
        if (wndconfig->floating)
            [window->ns.object setLevel:NSFloatingWindowLevel];
827
828
829

        if (wndconfig->maximized)
            [window->ns.object zoom:nil];
830
    }
Camilla Berglund's avatar
Camilla Berglund committed
831

832
    if (strlen(wndconfig->ns.frameName))
833
        [window->ns.object setFrameAutosaveName:@(wndconfig->ns.frameName)];
Camilla Löwy's avatar
Camilla Löwy committed
834

Camilla Berglund's avatar
Camilla Berglund committed
835
    window->ns.view = [[GLFWContentView alloc] initWithGlfwWindow:window];
836
    window->ns.retina = wndconfig->ns.retina;
Camilla Berglund's avatar
Camilla Berglund committed
837

838
    if (fbconfig->transparent)
Cem Karan's avatar
Cem Karan committed
839
840
    {
        [window->ns.object setOpaque:NO];
841
        [window->ns.object setHasShadow:NO];
Cem Karan's avatar
Cem Karan committed
842
843
844
        [window->ns.object setBackgroundColor:[NSColor clearColor]];
    }

845
    [window->ns.object setContentView:window->ns.view];
846
    [window->ns.object makeFirstResponder:window->ns.view];
847
    [window->ns.object setTitle:@(wndconfig->title)];
848
849
    [window->ns.object setDelegate:window->ns.delegate];
    [window->ns.object setAcceptsMouseMovedEvents:YES];
850
    [window->ns.object setRestorable:NO];
851

852
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200
853
854
    if ([window->ns.object respondsToSelector:@selector(setTabbingMode:)])
        [window->ns.object setTabbingMode:NSWindowTabbingModeDisallowed];
855
#endif
856

857
858
859
    _glfwPlatformGetWindowSize(window, &window->ns.width, &window->ns.height);
    _glfwPlatformGetFramebufferSize(window, &window->ns.fbWidth, &window->ns.fbHeight);

860
    return GLFW_TRUE;
861
}
Camilla Berglund's avatar
Camilla Berglund committed
862

Camilla Berglund's avatar
Camilla Berglund committed
863

864
865
866
867
868
869
870
871
//////////////////////////////////////////////////////////////////////////
//////                       GLFW internal API                      //////
//////////////////////////////////////////////////////////////////////////

// Transforms a y-coordinate between the CG display and NS screen spaces
//
float _glfwTransformYNS(float y)
{
872
    return CGDisplayBounds(CGMainDisplayID()).size.height - y - 1;
873
874
875
}


876
877
878
879
//////////////////////////////////////////////////////////////////////////
//////                       GLFW platform API                      //////
//////////////////////////////////////////////////////////////////////////

880
881
int _glfwPlatformCreateWindow(_GLFWwindow* window,
                              const _GLFWwndconfig* wndconfig,
882
                              const _GLFWctxconfig* ctxconfig,
883
                              const _GLFWfbconfig* fbconfig)
884
{
885
886
    @autoreleasepool {

887
888
    if (!_glfw.ns.finishedLaunching)
        [NSApp run];
889

890
    if (!createNativeWindow(window, wndconfig, fbconfig))
891
        return GLFW_FALSE;
892

893
    if (ctxconfig->client != GLFW_NO_API)
894
    {
895
896
        if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API)
        {
897
898
            if (!_glfwInitNSGL())
                return GLFW_FALSE;
899
900
901
            if (!_glfwCreateContextNSGL(window, ctxconfig, fbconfig))
                return GLFW_FALSE;
        }
Camilla Löwy's avatar
Camilla Löwy committed
902
        else if (ctxconfig->source == GLFW_EGL_CONTEXT_API)
903
        {
Camilla Löwy's avatar
Camilla Löwy committed
904
905
906
907
            if (!_glfwInitEGL())
                return GLFW_FALSE;
            if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig))
                return GLFW_FALSE;
908
        }
Camilla Löwy's avatar
Camilla Löwy committed
909
910
911
912
913
914
915
        else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API)
        {
            if (!_glfwInitOSMesa())
                return GLFW_FALSE;
            if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig))
                return GLFW_FALSE;
        }
916
    }
Camilla Berglund's avatar
Camilla Berglund committed
917

918
    if (window->monitor)
919
920
    {
        _glfwPlatformShowWindow(window);
Camilla Berglund's avatar
Camilla Berglund committed
921
        _glfwPlatformFocusWindow(window);
Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
922
        acquireMonitor(window);
923
    }
Camilla Berglund's avatar
Camilla Berglund committed
924

925
    return GLFW_TRUE;
926
927

    } // autoreleasepool
Camilla Berglund's avatar
Camilla Berglund committed
928
929
}

930
void _glfwPlatformDestroyWindow(_GLFWwindow* window)
Camilla Berglund's avatar
Camilla Berglund committed
931
{
932
933
    @autoreleasepool {

934
935
936
    if (_glfw.ns.disabledCursorWindow == window)
        _glfw.ns.disabledCursorWindow = NULL;

937
    [window->ns.object orderOut:nil];
Camilla Berglund's avatar
Camilla Berglund committed
938

939
    if (window->monitor)
940
        releaseMonitor(window);
Camilla Berglund's avatar
Camilla Berglund committed
941

942
    if (window->context.destroy)
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
943
        window->context.destroy(window);
Camilla Berglund's avatar
Camilla Berglund committed
944

945
946
947
    [window->ns.object setDelegate:nil];
    [window->ns.delegate release];
    window->ns.delegate = nil;