cocoa_window.m 50 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
38
39
40
// Returns the style mask corresponding to the window settings
//
static NSUInteger getStyleMask(_GLFWwindow* window)
{
    NSUInteger styleMask = 0;

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

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

    return styleMask;
}

Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
55
// Returns whether the cursor is in the content area of the specified window
56
//
Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
57
static GLFWbool cursorInContentArea(_GLFWwindow* window)
58
{
59
60
61
62
    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
63
// Hides the cursor if not already hidden
64
//
Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
65
static void hideCursor(_GLFWwindow* window)
66
{
Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
67
68
    if (!_glfw.ns.cursorHidden)
    {
69
        [NSCursor hide];
Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
70
71
72
        _glfw.ns.cursorHidden = GLFW_TRUE;
    }
}
73

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

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

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

102
103
104
105
106
107
108
109
110
111
// 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
112
        _glfwCenterCursorInContentArea(window);
113
114
115
116
117
118
119
120
121
122
123
        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
124
    if (cursorInContentArea(window))
125
126
127
        updateCursorImage(window);
}

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

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

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

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

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

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

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

    return mods;
}

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

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

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

    return 0;
}

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

214

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

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

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

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

@implementation GLFWWindowDelegate

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

    return self;
}

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

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

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

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

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

263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
    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
278
279
}

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

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

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

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

298
    _glfwInputWindowIconify(window, GLFW_TRUE);
299
300
}

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

306
    _glfwInputWindowIconify(window, GLFW_FALSE);
307
308
}

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

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

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

323
    _glfwInputWindowFocus(window, GLFW_FALSE);
324
325
}

326
327
328
329
330
331
- (void)windowDidChangeScreen:(NSNotification *)notification
{
    if (window->context.source == GLFW_NATIVE_CONTEXT_API)
        _glfwUpdateDisplayLinkDisplayNSGL(window);
}

332
333
@end

Camilla Berglund's avatar
Camilla Berglund committed
334

335
//------------------------------------------------------------------------
Camilla Berglund's avatar
Camilla Berglund committed
336
// Content view class for the GLFW window
337
//------------------------------------------------------------------------
Camilla Berglund's avatar
Camilla Berglund committed
338

339
@interface GLFWContentView : NSView <NSTextInputClient>
340
341
{
    _GLFWwindow* window;
342
    NSTrackingArea* trackingArea;
343
    NSMutableAttributedString* markedText;
344
345
}

346
- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow;
347

Camilla Berglund's avatar
Camilla Berglund committed
348
349
350
351
@end

@implementation GLFWContentView

352
- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow
353
{
Camilla Berglund's avatar
Camilla Berglund committed
354
    self = [super init];
355
    if (self != nil)
356
    {
357
        window = initWindow;
358
        trackingArea = nil;
359
        markedText = [[NSMutableAttributedString alloc] init];
Camilla Berglund's avatar
Camilla Berglund committed
360

361
        [self updateTrackingAreas];
362
363
364
        // NOTE: kUTTypeURL corresponds to NSPasteboardTypeURL but is available
        //       on 10.7 without having been deprecated yet
        [self registerForDraggedTypes:@[(__bridge NSString*) kUTTypeURL]];
365
    }
366
367
368
369

    return self;
}

370
- (void)dealloc
371
372
{
    [trackingArea release];
373
    [markedText release];
374
375
376
    [super dealloc];
}

Camilla Berglund's avatar
Camilla Berglund committed
377
378
- (BOOL)isOpaque
{
379
    return [window->ns.object isOpaque];
Camilla Berglund's avatar
Camilla Berglund committed
380
381
382
383
384
385
386
387
388
389
390
391
}

- (BOOL)canBecomeKeyView
{
    return YES;
}

- (BOOL)acceptsFirstResponder
{
    return YES;
}

392
393
394
395
396
- (BOOL)wantsUpdateLayer
{
    return YES;
}

397
398
399
400
401
402
403
404
- (void)updateLayer
{
    if (window->context.client != GLFW_NO_API)
        [window->context.nsgl.object update];

    _glfwInputWindowDamage(window);
}

405
406
- (void)cursorUpdate:(NSEvent *)event
{
407
    updateCursorImage(window);
408
409
}

410
411
412
413
414
- (BOOL)acceptsFirstMouse:(NSEvent *)event
{
    return YES;
}

Camilla Berglund's avatar
Camilla Berglund committed
415
416
- (void)mouseDown:(NSEvent *)event
{
417
418
419
    _glfwInputMouseClick(window,
                         GLFW_MOUSE_BUTTON_LEFT,
                         GLFW_PRESS,
420
                         translateFlags([event modifierFlags]));
Camilla Berglund's avatar
Camilla Berglund committed
421
422
423
424
425
426
427
428
429
}

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

- (void)mouseUp:(NSEvent *)event
{
430
431
432
    _glfwInputMouseClick(window,
                         GLFW_MOUSE_BUTTON_LEFT,
                         GLFW_RELEASE,
433
                         translateFlags([event modifierFlags]));
Camilla Berglund's avatar
Camilla Berglund committed
434
435
436
437
}

- (void)mouseMoved:(NSEvent *)event
{
438
    if (window->cursorMode == GLFW_CURSOR_DISABLED)
439
    {
440
441
442
443
444
445
        const double dx = [event deltaX] - window->ns.cursorWarpDeltaX;
        const double dy = [event deltaY] - window->ns.cursorWarpDeltaY;

        _glfwInputCursorPos(window,
                            window->virtualCursorPosX + dx,
                            window->virtualCursorPosY + dy);
446
    }
Camilla Berglund's avatar
Camilla Berglund committed
447
448
    else
    {
Camilla Berglund's avatar
Camilla Berglund committed
449
        const NSRect contentRect = [window->ns.view frame];
450
        // NOTE: The returned location uses base 0,1 not 0,0
451
        const NSPoint pos = [event locationInWindow];
Camilla Berglund's avatar
Camilla Berglund committed
452

453
        _glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y);
Camilla Berglund's avatar
Camilla Berglund committed
454
    }
455

456
457
    window->ns.cursorWarpDeltaX = 0;
    window->ns.cursorWarpDeltaY = 0;
Camilla Berglund's avatar
Camilla Berglund committed
458
459
460
461
}

- (void)rightMouseDown:(NSEvent *)event
{
462
463
464
    _glfwInputMouseClick(window,
                         GLFW_MOUSE_BUTTON_RIGHT,
                         GLFW_PRESS,
465
                         translateFlags([event modifierFlags]));
Camilla Berglund's avatar
Camilla Berglund committed
466
467
468
469
470
471
472
473
474
}

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

- (void)rightMouseUp:(NSEvent *)event
{
475
476
477
    _glfwInputMouseClick(window,
                         GLFW_MOUSE_BUTTON_RIGHT,
                         GLFW_RELEASE,
478
                         translateFlags([event modifierFlags]));
Camilla Berglund's avatar
Camilla Berglund committed
479
480
481
482
}

- (void)otherMouseDown:(NSEvent *)event
{
483
    _glfwInputMouseClick(window,
484
                         (int) [event buttonNumber],
485
                         GLFW_PRESS,
486
                         translateFlags([event modifierFlags]));
Camilla Berglund's avatar
Camilla Berglund committed
487
488
489
490
491
492
493
494
495
}

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

- (void)otherMouseUp:(NSEvent *)event
{
496
    _glfwInputMouseClick(window,
497
                         (int) [event buttonNumber],
498
                         GLFW_RELEASE,
499
                         translateFlags([event modifierFlags]));
Camilla Berglund's avatar
Camilla Berglund committed
500
501
}

502
503
- (void)mouseExited:(NSEvent *)event
{
504
    if (window->cursorMode == GLFW_CURSOR_HIDDEN)
Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
505
        showCursor(window);
506

507
    _glfwInputCursorEnter(window, GLFW_FALSE);
508
509
510
511
}

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

515
    _glfwInputCursorEnter(window, GLFW_TRUE);
516
517
}

518
519
520
- (void)viewDidChangeBackingProperties
{
    const NSRect contentRect = [window->ns.view frame];
521
    const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect];
522

523
524
525
526
527
528
529
    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);
    }
530
531
532
533
534
535
536
537
538

    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);
539

540
        if (window->ns.retina && window->ns.layer)
541
            [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]];
542
    }
543
544
545
546
}

- (void)drawRect:(NSRect)rect
{
547
    _glfwInputWindowDamage(window);
548
549
}

550
551
552
553
554
555
556
557
- (void)updateTrackingAreas
{
    if (trackingArea != nil)
    {
        [self removeTrackingArea:trackingArea];
        [trackingArea release];
    }

558
559
    const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
                                          NSTrackingActiveInKeyWindow |
560
                                          NSTrackingEnabledDuringMouseDrag |
561
562
563
                                          NSTrackingCursorUpdate |
                                          NSTrackingInVisibleRect |
                                          NSTrackingAssumeInside;
564
565
566
567
568
569
570

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

    [self addTrackingArea:trackingArea];
571
    [super updateTrackingAreas];
572
573
}

Camilla Berglund's avatar
Camilla Berglund committed
574
575
- (void)keyDown:(NSEvent *)event
{
576
577
    const int key = translateKey([event keyCode]);
    const int mods = translateFlags([event modifierFlags]);
578

579
    _glfwInputKey(window, key, [event keyCode], GLFW_PRESS, mods);
580

581
    [self interpretKeyEvents:@[event]];
Camilla Berglund's avatar
Camilla Berglund committed
582
583
584
585
}

- (void)flagsChanged:(NSEvent *)event
{
586
    int action;
587
    const unsigned int modifierFlags =
588
        [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
589
590
    const int key = translateKey([event keyCode]);
    const int mods = translateFlags(modifierFlags);
Xo Wang's avatar
Xo Wang committed
591
    const NSUInteger keyFlag = translateKeyToModifierFlag(key);
Camilla Berglund's avatar
Camilla Berglund committed
592

Xo Wang's avatar
Xo Wang committed
593
    if (keyFlag & modifierFlags)
594
    {
595
        if (window->keys[key] == GLFW_PRESS)
596
597
598
599
            action = GLFW_RELEASE;
        else
            action = GLFW_PRESS;
    }
Camilla Berglund's avatar
Camilla Berglund committed
600
    else
Camilla Berglund's avatar
Camilla Berglund committed
601
        action = GLFW_RELEASE;
Camilla Berglund's avatar
Camilla Berglund committed
602

603
    _glfwInputKey(window, key, [event keyCode], action, mods);
Camilla Berglund's avatar
Camilla Berglund committed
604
605
606
607
}

- (void)keyUp:(NSEvent *)event
{
608
609
610
    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
611
612
613
614
}

- (void)scrollWheel:(NSEvent *)event
{
615
616
    double deltaX = [event scrollingDeltaX];
    double deltaY = [event scrollingDeltaY];
617

618
    if ([event hasPreciseScrollingDeltas])
619
    {
620
621
        deltaX *= 0.1;
        deltaY *= 0.1;
622
    }
623
624

    if (fabs(deltaX) > 0.0 || fabs(deltaY) > 0.0)
625
        _glfwInputScroll(window, deltaX, deltaY);
Camilla Berglund's avatar
Camilla Berglund committed
626
627
}

arturo's avatar
arturo committed
628
629
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
630
631
632
    // 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
633
634
}

Camilla Berglund's avatar
Camilla Berglund committed
635
636
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
637
    const NSRect contentRect = [window->ns.view frame];
638
639
640
    // 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
641

642
643
644
645
646
    NSPasteboard* pasteboard = [sender draggingPasteboard];
    NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES};
    NSArray* urls = [pasteboard readObjectsForClasses:@[[NSURL class]]
                                              options:options];
    const NSUInteger count = [urls count];
647
    if (count)
Camilla Berglund's avatar
Camilla Berglund committed
648
    {
649
        char** paths = calloc(count, sizeof(char*));
Camilla Berglund's avatar
Camilla Berglund committed
650

651
        for (NSUInteger i = 0;  i < count;  i++)
652
            paths[i] = _glfw_strdup([urls[i] fileSystemRepresentation]);
Camilla Berglund's avatar
Camilla Berglund committed
653

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

656
        for (NSUInteger i = 0;  i < count;  i++)
657
658
            free(paths[i]);
        free(paths);
Camilla Berglund's avatar
Camilla Berglund committed
659
660
661
    }

    return YES;
arturo's avatar
arturo committed
662
663
}

664
665
- (BOOL)hasMarkedText
{
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
666
    return [markedText length] > 0;
667
668
669
670
}

- (NSRange)markedRange
{
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
671
672
673
674
    if ([markedText length] > 0)
        return NSMakeRange(0, [markedText length] - 1);
    else
        return kEmptyRange;
675
676
677
678
679
680
681
}

- (NSRange)selectedRange
{
    return kEmptyRange;
}

Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
682
- (void)setMarkedText:(id)string
683
684
685
        selectedRange:(NSRange)selectedRange
     replacementRange:(NSRange)replacementRange
{
686
    [markedText release];
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
687
    if ([string isKindOfClass:[NSAttributedString class]])
688
        markedText = [[NSMutableAttributedString alloc] initWithAttributedString:string];
689
    else
690
        markedText = [[NSMutableAttributedString alloc] initWithString:string];
691
692
693
694
695
696
697
698
699
700
701
702
}

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

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

Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
703
- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
704
705
706
707
708
                                               actualRange:(NSRangePointer)actualRange
{
    return nil;
}

Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
709
- (NSUInteger)characterIndexForPoint:(NSPoint)point
710
711
712
713
{
    return 0;
}

Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
714
- (NSRect)firstRectForCharacterRange:(NSRange)range
715
716
                         actualRange:(NSRangePointer)actualRange
{
717
718
    const NSRect frame = [window->ns.view frame];
    return NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0);
719
720
}

Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
721
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
722
{
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
723
    NSString* characters;
724
725
    NSEvent* event = [NSApp currentEvent];
    const int mods = translateFlags([event modifierFlags]);
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
726
    const int plain = !(mods & GLFW_MOD_SUPER);
727

Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
728
729
730
731
    if ([string isKindOfClass:[NSAttributedString class]])
        characters = [string string];
    else
        characters = (NSString*) string;
732

733
734
    const NSUInteger length = [characters length];
    for (NSUInteger i = 0;  i < length;  i++)
735
736
737
738
739
740
741
742
743
    {
        const unichar codepoint = [characters characterAtIndex:i];
        if ((codepoint & 0xff00) == 0xf700)
            continue;

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

744
745
746
747
- (void)doCommandBySelector:(SEL)selector
{
}

Camilla Berglund's avatar
Camilla Berglund committed
748
749
@end

Camilla Berglund's avatar
Camilla Berglund committed
750

Camilla Berglund's avatar
Camilla Berglund committed
751
752
753
754
755
756
757
758
759
760
761
//------------------------------------------------------------------------
// GLFW window class
//------------------------------------------------------------------------

@interface GLFWWindow : NSWindow {}
@end

@implementation GLFWWindow

- (BOOL)canBecomeKeyWindow
{
762
    // Required for NSWindowStyleMaskBorderless windows
Camilla Berglund's avatar
Camilla Berglund committed
763
764
765
    return YES;
}

766
767
768
769
770
- (BOOL)canBecomeMainWindow
{
    return YES;
}

Camilla Berglund's avatar
Camilla Berglund committed
771
772
773
@end


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

788
789
    NSRect contentRect;

790
    if (window->monitor)
791
792
793
794
795
796
797
798
799
    {
        GLFWvidmode mode;
        int xpos, ypos;

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

        contentRect = NSMakeRect(xpos, ypos, mode.width, mode.height);
    }
800
801
802
    else
        contentRect = NSMakeRect(0, 0, wndconfig->width, wndconfig->height);

803
    window->ns.object = [[GLFWWindow alloc]
804
        initWithContentRect:contentRect
Camilla Berglund's avatar
Camilla Berglund committed
805
                  styleMask:getStyleMask(window)
806
807
808
                    backing:NSBackingStoreBuffered
                      defer:NO];

809
    if (window->ns.object == nil)
810
    {
811
        _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create window");
812
        return GLFW_FALSE;
813
814
    }

815
    if (window->monitor)
816
817
818
819
        [window->ns.object setLevel:NSMainMenuWindowLevel + 1];
    else
    {
        [window->ns.object center];
820
821
822
        _glfw.ns.cascadePoint =
            NSPointToCGPoint([window->ns.object cascadeTopLeftFromPoint:
                              NSPointFromCGPoint(_glfw.ns.cascadePoint)]);
823

Camilla Berglund's avatar
Camilla Berglund committed
824
        if (wndconfig->resizable)
825
826
827
828
829
830
        {
            const NSWindowCollectionBehavior behavior =
                NSWindowCollectionBehaviorFullScreenPrimary |
                NSWindowCollectionBehaviorManaged;
            [window->ns.object setCollectionBehavior:behavior];
        }
Camilla Berglund's avatar
Camilla Berglund committed
831

832
833
        if (wndconfig->floating)
            [window->ns.object setLevel:NSFloatingWindowLevel];
834
835
836

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

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

Camilla Berglund's avatar
Camilla Berglund committed
842
    window->ns.view = [[GLFWContentView alloc] initWithGlfwWindow:window];
843
    window->ns.retina = wndconfig->ns.retina;
Camilla Berglund's avatar
Camilla Berglund committed
844

845
    if (fbconfig->transparent)
Cem Karan's avatar
Cem Karan committed
846
847
    {
        [window->ns.object setOpaque:NO];
848
        [window->ns.object setHasShadow:NO];
Cem Karan's avatar
Cem Karan committed
849
850
851
        [window->ns.object setBackgroundColor:[NSColor clearColor]];
    }

852
    [window->ns.object setContentView:window->ns.view];
853
    [window->ns.object makeFirstResponder:window->ns.view];
854
    [window->ns.object setTitle:@(wndconfig->title)];
855
856
    [window->ns.object setDelegate:window->ns.delegate];
    [window->ns.object setAcceptsMouseMovedEvents:YES];
857
    [window->ns.object setRestorable:NO];
858

859
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200
860
861
    if ([window->ns.object respondsToSelector:@selector(setTabbingMode:)])
        [window->ns.object setTabbingMode:NSWindowTabbingModeDisallowed];
862
#endif
863

864
865
866
    _glfwPlatformGetWindowSize(window, &window->ns.width, &window->ns.height);
    _glfwPlatformGetFramebufferSize(window, &window->ns.fbWidth, &window->ns.fbHeight);

867
    return GLFW_TRUE;
868
}
Camilla Berglund's avatar
Camilla Berglund committed
869

Camilla Berglund's avatar
Camilla Berglund committed
870

871
872
873
874
875
876
877
878
//////////////////////////////////////////////////////////////////////////
//////                       GLFW internal API                      //////
//////////////////////////////////////////////////////////////////////////

// Transforms a y-coordinate between the CG display and NS screen spaces
//
float _glfwTransformYNS(float y)
{
879
    return CGDisplayBounds(CGMainDisplayID()).size.height - y - 1;
880
881
882
}


883
884
885
886
//////////////////////////////////////////////////////////////////////////
//////                       GLFW platform API                      //////
//////////////////////////////////////////////////////////////////////////

887
888
int _glfwPlatformCreateWindow(_GLFWwindow* window,
                              const _GLFWwndconfig* wndconfig,
889
                              const _GLFWctxconfig* ctxconfig,
890
                              const _GLFWfbconfig* fbconfig)
891
{
892
893
    @autoreleasepool {

894
895
896
897
898
    if (!_glfw.ns.finishedLaunching)
    {
        [NSApp run];
        _glfw.ns.finishedLaunching = GLFW_TRUE;
    }
899

900
    if (!createNativeWindow(window, wndconfig, fbconfig))
901
        return GLFW_FALSE;
902

903
    if (ctxconfig->client != GLFW_NO_API)
904
    {
905
906
        if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API)
        {
907
908
            if (!_glfwInitNSGL())
                return GLFW_FALSE;
909
910
911
            if (!_glfwCreateContextNSGL(window, ctxconfig, fbconfig))
                return GLFW_FALSE;
        }
Camilla Löwy's avatar
Camilla Löwy committed
912
        else if (ctxconfig->source == GLFW_EGL_CONTEXT_API)
913
        {
Camilla Löwy's avatar
Camilla Löwy committed
914
915
916
917
            if (!_glfwInitEGL())
                return GLFW_FALSE;
            if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig))
                return GLFW_FALSE;
918
        }
Camilla Löwy's avatar
Camilla Löwy committed
919
920
921
922
923
924
925
        else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API)
        {
            if (!_glfwInitOSMesa())
                return GLFW_FALSE;
            if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig))
                return GLFW_FALSE;
        }
926
    }
Camilla Berglund's avatar
Camilla Berglund committed
927

928
    if (window->monitor)
929
930
    {
        _glfwPlatformShowWindow(window);
Camilla Berglund's avatar
Camilla Berglund committed
931
        _glfwPlatformFocusWindow(window);
Camilla Löwy's avatar
Cleanup    
Camilla Löwy committed
932
        acquireMonitor(window);
933
    }
Camilla Berglund's avatar
Camilla Berglund committed
934

935
    return GLFW_TRUE;
936
937

    } // autoreleasepool
Camilla Berglund's avatar
Camilla Berglund committed
938
939
}

940
void _glfwPlatformDestroyWindow(_GLFWwindow* window)
Camilla Berglund's avatar
Camilla Berglund committed
941
{
942
943
    @autoreleasepool {

944
945
946
    if (_glfw.ns.disabledCursorWindow == window)
        _glfw.ns.disabledCursorWindow = NULL;

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

949
    if (window->monitor)
950
        releaseMonitor(window);
Camilla Berglund's avatar
Camilla Berglund committed
951

952
    if (window->context.destroy)
Camilla Berglund's avatar
Cleanup    
Camilla Berglund committed
953
        window->context.destroy(window);
Camilla Berglund's avatar
Camilla Berglund committed
954