• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1//
2// Copyright 2015 The ANGLE Project Authors. All rights reserved.
3// Use of this source code is governed by a BSD-style license that can be
4// found in the LICENSE file.
5//
6
7// OSXWindow.mm: Implementation of OSWindow for OSX
8
9#include "util/osx/OSXWindow.h"
10
11#include <set>
12// Include Carbon to use the keycode names in Carbon's Event.h
13#include <Carbon/Carbon.h>
14
15#include "anglebase/no_destructor.h"
16#include "common/debug.h"
17
18// On OSX 10.12 a number of AppKit interfaces have been renamed for consistency, and the previous
19// symbols tagged as deprecated. However we can't simply use the new symbols as it would break
20// compilation on our automated testing that doesn't use OSX 10.12 yet. So we just ignore the
21// warnings.
22#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
23
24// Some events such as "ShouldTerminate" are sent to the whole application so we keep a list of
25// all the windows in order to forward the event to each of them. However this and calling pushEvent
26// in ApplicationDelegate is inherently unsafe in a multithreaded environment.
27static std::set<OSXWindow *> &AllWindows()
28{
29    static angle::base::NoDestructor<std::set<OSXWindow *>> allWindows;
30    return *allWindows;
31}
32
33@interface Application : NSApplication
34@end
35
36@implementation Application
37- (void)sendEvent:(NSEvent *)nsEvent
38{
39    if ([nsEvent type] == NSApplicationDefined)
40    {
41        for (auto window : AllWindows())
42        {
43            if ([window->getNSWindow() windowNumber] == [nsEvent windowNumber])
44            {
45                Event event;
46                event.Type = Event::EVENT_TEST;
47                window->pushEvent(event);
48            }
49        }
50    }
51    [super sendEvent:nsEvent];
52}
53
54// Override internal method to try to diagnose unexpected crashes in Core Animation.
55// anglebug.com/6570
56// See also:
57//   https://github.com/microsoft/appcenter-sdk-apple/issues/1944
58//   https://stackoverflow.com/questions/220159/how-do-you-print-out-a-stack-trace-to-the-console-log-in-cocoa
59- (void)_crashOnException:(NSException *)exception
60{
61    NSLog(@"*** OSXWindow aborting on exception:  <%@> %@", [exception name], [exception reason]);
62    NSLog(@"%@", [exception callStackSymbols]);
63    abort();
64}
65@end
66
67// The Delegate receiving application-wide events.
68@interface ApplicationDelegate : NSObject
69@end
70
71@implementation ApplicationDelegate
72- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
73{
74    Event event;
75    event.Type = Event::EVENT_CLOSED;
76    for (auto window : AllWindows())
77    {
78        window->pushEvent(event);
79    }
80    return NSTerminateCancel;
81}
82@end
83static ApplicationDelegate *gApplicationDelegate = nil;
84
85static bool InitializeAppKit()
86{
87    if (NSApp != nil)
88    {
89        return true;
90    }
91
92    // Initialize the global variable "NSApp"
93    [Application sharedApplication];
94
95    // Make us appear in the dock
96    [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
97
98    // Register our global event handler
99    gApplicationDelegate = [[ApplicationDelegate alloc] init];
100    if (gApplicationDelegate == nil)
101    {
102        return false;
103    }
104    [NSApp setDelegate:static_cast<id>(gApplicationDelegate)];
105
106    // Set our status to "started" so we are not bouncing in the doc and can activate
107    [NSApp finishLaunching];
108    return true;
109}
110
111// NS's and CG's coordinate systems start at the bottom left, while OSWindow's coordinate
112// system starts at the top left. This function converts the Y coordinate accordingly.
113static float YCoordToFromCG(float y)
114{
115    float screenHeight = CGDisplayBounds(CGMainDisplayID()).size.height;
116    return screenHeight - y;
117}
118
119// Delegate for window-wide events, note that the protocol doesn't contain anything input related.
120@implementation WindowDelegate
121- (id)initWithWindow:(OSXWindow *)window
122{
123    self = [super init];
124    if (self != nil)
125    {
126        mWindow = window;
127    }
128    return self;
129}
130
131- (void)onOSXWindowDeleted
132{
133    mWindow = nil;
134}
135
136- (BOOL)windowShouldClose:(id)sender
137{
138    Event event;
139    event.Type = Event::EVENT_CLOSED;
140    mWindow->pushEvent(event);
141    return NO;
142}
143
144- (void)windowDidResize:(NSNotification *)notification
145{
146    NSSize windowSize = [[mWindow->getNSWindow() contentView] frame].size;
147    Event event;
148    event.Type        = Event::EVENT_RESIZED;
149    event.Size.Width  = (int)windowSize.width;
150    event.Size.Height = (int)windowSize.height;
151    mWindow->pushEvent(event);
152}
153
154- (void)windowDidMove:(NSNotification *)notification
155{
156    NSRect screenspace = [mWindow->getNSWindow() frame];
157    Event event;
158    event.Type   = Event::EVENT_MOVED;
159    event.Move.X = (int)screenspace.origin.x;
160    event.Move.Y = (int)YCoordToFromCG(screenspace.origin.y + screenspace.size.height);
161    mWindow->pushEvent(event);
162}
163
164- (void)windowDidBecomeKey:(NSNotification *)notification
165{
166    Event event;
167    event.Type = Event::EVENT_GAINED_FOCUS;
168    mWindow->pushEvent(event);
169    [self retain];
170}
171
172- (void)windowDidResignKey:(NSNotification *)notification
173{
174    if (mWindow != nil)
175    {
176        Event event;
177        event.Type = Event::EVENT_LOST_FOCUS;
178        mWindow->pushEvent(event);
179    }
180    [self release];
181}
182@end
183
184static Key NSCodeToKey(int keyCode)
185{
186    // Missing KEY_PAUSE
187    switch (keyCode)
188    {
189        case kVK_Shift:
190            return KEY_LSHIFT;
191        case kVK_RightShift:
192            return KEY_RSHIFT;
193        case kVK_Option:
194            return KEY_LALT;
195        case kVK_RightOption:
196            return KEY_RALT;
197        case kVK_Control:
198            return KEY_LCONTROL;
199        case kVK_RightControl:
200            return KEY_RCONTROL;
201        case kVK_Command:
202            return KEY_LSYSTEM;
203        // Right System doesn't have a name, but shows up as 0x36.
204        case 0x36:
205            return KEY_RSYSTEM;
206        case kVK_Function:
207            return KEY_MENU;
208
209        case kVK_ANSI_Semicolon:
210            return KEY_SEMICOLON;
211        case kVK_ANSI_Slash:
212            return KEY_SLASH;
213        case kVK_ANSI_Equal:
214            return KEY_EQUAL;
215        case kVK_ANSI_Minus:
216            return KEY_DASH;
217        case kVK_ANSI_LeftBracket:
218            return KEY_LBRACKET;
219        case kVK_ANSI_RightBracket:
220            return KEY_RBRACKET;
221        case kVK_ANSI_Comma:
222            return KEY_COMMA;
223        case kVK_ANSI_Period:
224            return KEY_PERIOD;
225        case kVK_ANSI_Backslash:
226            return KEY_BACKSLASH;
227        case kVK_ANSI_Grave:
228            return KEY_TILDE;
229        case kVK_Escape:
230            return KEY_ESCAPE;
231        case kVK_Space:
232            return KEY_SPACE;
233        case kVK_Return:
234            return KEY_RETURN;
235        case kVK_Delete:
236            return KEY_BACK;
237        case kVK_Tab:
238            return KEY_TAB;
239        case kVK_PageUp:
240            return KEY_PAGEUP;
241        case kVK_PageDown:
242            return KEY_PAGEDOWN;
243        case kVK_End:
244            return KEY_END;
245        case kVK_Home:
246            return KEY_HOME;
247        case kVK_Help:
248            return KEY_INSERT;
249        case kVK_ForwardDelete:
250            return KEY_DELETE;
251        case kVK_ANSI_KeypadPlus:
252            return KEY_ADD;
253        case kVK_ANSI_KeypadMinus:
254            return KEY_SUBTRACT;
255        case kVK_ANSI_KeypadMultiply:
256            return KEY_MULTIPLY;
257        case kVK_ANSI_KeypadDivide:
258            return KEY_DIVIDE;
259
260        case kVK_F1:
261            return KEY_F1;
262        case kVK_F2:
263            return KEY_F2;
264        case kVK_F3:
265            return KEY_F3;
266        case kVK_F4:
267            return KEY_F4;
268        case kVK_F5:
269            return KEY_F5;
270        case kVK_F6:
271            return KEY_F6;
272        case kVK_F7:
273            return KEY_F7;
274        case kVK_F8:
275            return KEY_F8;
276        case kVK_F9:
277            return KEY_F9;
278        case kVK_F10:
279            return KEY_F10;
280        case kVK_F11:
281            return KEY_F11;
282        case kVK_F12:
283            return KEY_F12;
284        case kVK_F13:
285            return KEY_F13;
286        case kVK_F14:
287            return KEY_F14;
288        case kVK_F15:
289            return KEY_F15;
290
291        case kVK_LeftArrow:
292            return KEY_LEFT;
293        case kVK_RightArrow:
294            return KEY_RIGHT;
295        case kVK_DownArrow:
296            return KEY_DOWN;
297        case kVK_UpArrow:
298            return KEY_UP;
299
300        case kVK_ANSI_Keypad0:
301            return KEY_NUMPAD0;
302        case kVK_ANSI_Keypad1:
303            return KEY_NUMPAD1;
304        case kVK_ANSI_Keypad2:
305            return KEY_NUMPAD2;
306        case kVK_ANSI_Keypad3:
307            return KEY_NUMPAD3;
308        case kVK_ANSI_Keypad4:
309            return KEY_NUMPAD4;
310        case kVK_ANSI_Keypad5:
311            return KEY_NUMPAD5;
312        case kVK_ANSI_Keypad6:
313            return KEY_NUMPAD6;
314        case kVK_ANSI_Keypad7:
315            return KEY_NUMPAD7;
316        case kVK_ANSI_Keypad8:
317            return KEY_NUMPAD8;
318        case kVK_ANSI_Keypad9:
319            return KEY_NUMPAD9;
320
321        case kVK_ANSI_A:
322            return KEY_A;
323        case kVK_ANSI_B:
324            return KEY_B;
325        case kVK_ANSI_C:
326            return KEY_C;
327        case kVK_ANSI_D:
328            return KEY_D;
329        case kVK_ANSI_E:
330            return KEY_E;
331        case kVK_ANSI_F:
332            return KEY_F;
333        case kVK_ANSI_G:
334            return KEY_G;
335        case kVK_ANSI_H:
336            return KEY_H;
337        case kVK_ANSI_I:
338            return KEY_I;
339        case kVK_ANSI_J:
340            return KEY_J;
341        case kVK_ANSI_K:
342            return KEY_K;
343        case kVK_ANSI_L:
344            return KEY_L;
345        case kVK_ANSI_M:
346            return KEY_M;
347        case kVK_ANSI_N:
348            return KEY_N;
349        case kVK_ANSI_O:
350            return KEY_O;
351        case kVK_ANSI_P:
352            return KEY_P;
353        case kVK_ANSI_Q:
354            return KEY_Q;
355        case kVK_ANSI_R:
356            return KEY_R;
357        case kVK_ANSI_S:
358            return KEY_S;
359        case kVK_ANSI_T:
360            return KEY_T;
361        case kVK_ANSI_U:
362            return KEY_U;
363        case kVK_ANSI_V:
364            return KEY_V;
365        case kVK_ANSI_W:
366            return KEY_W;
367        case kVK_ANSI_X:
368            return KEY_X;
369        case kVK_ANSI_Y:
370            return KEY_Y;
371        case kVK_ANSI_Z:
372            return KEY_Z;
373
374        case kVK_ANSI_1:
375            return KEY_NUM1;
376        case kVK_ANSI_2:
377            return KEY_NUM2;
378        case kVK_ANSI_3:
379            return KEY_NUM3;
380        case kVK_ANSI_4:
381            return KEY_NUM4;
382        case kVK_ANSI_5:
383            return KEY_NUM5;
384        case kVK_ANSI_6:
385            return KEY_NUM6;
386        case kVK_ANSI_7:
387            return KEY_NUM7;
388        case kVK_ANSI_8:
389            return KEY_NUM8;
390        case kVK_ANSI_9:
391            return KEY_NUM9;
392        case kVK_ANSI_0:
393            return KEY_NUM0;
394    }
395
396    return Key(0);
397}
398
399static void AddNSKeyStateToEvent(Event *event, NSEventModifierFlags state)
400{
401    event->Key.Shift   = state & NSShiftKeyMask;
402    event->Key.Control = state & NSControlKeyMask;
403    event->Key.Alt     = state & NSAlternateKeyMask;
404    event->Key.System  = state & NSCommandKeyMask;
405}
406
407static MouseButton TranslateMouseButton(NSInteger button)
408{
409    switch (button)
410    {
411        case 2:
412            return MOUSEBUTTON_MIDDLE;
413        case 3:
414            return MOUSEBUTTON_BUTTON4;
415        case 4:
416            return MOUSEBUTTON_BUTTON5;
417        default:
418            return MOUSEBUTTON_UNKNOWN;
419    }
420}
421
422// Delegate for NSView events, mostly the input events
423@implementation ContentView
424- (id)initWithWindow:(OSXWindow *)window
425{
426    self = [super init];
427    if (self != nil)
428    {
429        mWindow          = window;
430        mTrackingArea    = nil;
431        mCurrentModifier = 0;
432        [self updateTrackingAreas];
433    }
434    return self;
435}
436
437- (void)dealloc
438{
439    [mTrackingArea release];
440    [super dealloc];
441}
442
443- (void)updateTrackingAreas
444{
445    if (mTrackingArea != nil)
446    {
447        [self removeTrackingArea:mTrackingArea];
448        [mTrackingArea release];
449        mTrackingArea = nil;
450    }
451
452    NSRect bounds               = [self bounds];
453    NSTrackingAreaOptions flags = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow |
454                                  NSTrackingCursorUpdate | NSTrackingInVisibleRect |
455                                  NSTrackingAssumeInside;
456    mTrackingArea = [[NSTrackingArea alloc] initWithRect:bounds
457                                                 options:flags
458                                                   owner:self
459                                                userInfo:nil];
460
461    [self addTrackingArea:mTrackingArea];
462    [super updateTrackingAreas];
463}
464
465// Helps with performance
466- (BOOL)isOpaque
467{
468    return YES;
469}
470
471- (BOOL)canBecomeKeyView
472{
473    return YES;
474}
475
476- (BOOL)acceptsFirstResponder
477{
478    return YES;
479}
480
481// Handle mouse events from the NSResponder protocol
482- (float)translateMouseY:(float)y
483{
484    return [self frame].size.height - y;
485}
486
487- (void)addButtonEvent:(NSEvent *)nsEvent
488                  type:(Event::EventType)eventType
489                button:(MouseButton)button
490{
491    Event event;
492    event.Type               = eventType;
493    event.MouseButton.Button = button;
494    event.MouseButton.X      = (int)[nsEvent locationInWindow].x;
495    event.MouseButton.Y      = (int)[self translateMouseY:[nsEvent locationInWindow].y];
496    mWindow->pushEvent(event);
497}
498
499- (void)mouseDown:(NSEvent *)event
500{
501    [self addButtonEvent:event type:Event::EVENT_MOUSE_BUTTON_PRESSED button:MOUSEBUTTON_LEFT];
502}
503
504- (void)mouseDragged:(NSEvent *)event
505{
506    [self mouseMoved:event];
507}
508
509- (void)mouseUp:(NSEvent *)event
510{
511    [self addButtonEvent:event type:Event::EVENT_MOUSE_BUTTON_RELEASED button:MOUSEBUTTON_LEFT];
512}
513
514- (void)mouseMoved:(NSEvent *)nsEvent
515{
516    Event event;
517    event.Type        = Event::EVENT_MOUSE_MOVED;
518    event.MouseMove.X = (int)[nsEvent locationInWindow].x;
519    event.MouseMove.Y = (int)[self translateMouseY:[nsEvent locationInWindow].y];
520    mWindow->pushEvent(event);
521}
522
523- (void)mouseEntered:(NSEvent *)nsEvent
524{
525    Event event;
526    event.Type = Event::EVENT_MOUSE_ENTERED;
527    mWindow->pushEvent(event);
528}
529
530- (void)mouseExited:(NSEvent *)nsEvent
531{
532    Event event;
533    event.Type = Event::EVENT_MOUSE_LEFT;
534    mWindow->pushEvent(event);
535}
536
537- (void)rightMouseDown:(NSEvent *)event
538{
539    [self addButtonEvent:event type:Event::EVENT_MOUSE_BUTTON_PRESSED button:MOUSEBUTTON_RIGHT];
540}
541
542- (void)rightMouseDragged:(NSEvent *)event
543{
544    [self mouseMoved:event];
545}
546
547- (void)rightMouseUp:(NSEvent *)event
548{
549    [self addButtonEvent:event type:Event::EVENT_MOUSE_BUTTON_RELEASED button:MOUSEBUTTON_RIGHT];
550}
551
552- (void)otherMouseDown:(NSEvent *)event
553{
554    [self addButtonEvent:event
555                    type:Event::EVENT_MOUSE_BUTTON_PRESSED
556                  button:TranslateMouseButton([event buttonNumber])];
557}
558
559- (void)otherMouseDragged:(NSEvent *)event
560{
561    [self mouseMoved:event];
562}
563
564- (void)otherMouseUp:(NSEvent *)event
565{
566    [self addButtonEvent:event
567                    type:Event::EVENT_MOUSE_BUTTON_RELEASED
568                  button:TranslateMouseButton([event buttonNumber])];
569}
570
571- (void)scrollWheel:(NSEvent *)nsEvent
572{
573    if (static_cast<int>([nsEvent deltaY]) == 0)
574    {
575        return;
576    }
577
578    Event event;
579    event.Type             = Event::EVENT_MOUSE_WHEEL_MOVED;
580    event.MouseWheel.Delta = (int)[nsEvent deltaY];
581    mWindow->pushEvent(event);
582}
583
584// Handle key events from the NSResponder protocol
585- (void)keyDown:(NSEvent *)nsEvent
586{
587    // TODO(cwallez) also send text events
588    Event event;
589    event.Type     = Event::EVENT_KEY_PRESSED;
590    event.Key.Code = NSCodeToKey([nsEvent keyCode]);
591    AddNSKeyStateToEvent(&event, [nsEvent modifierFlags]);
592    mWindow->pushEvent(event);
593}
594
595- (void)keyUp:(NSEvent *)nsEvent
596{
597    Event event;
598    event.Type     = Event::EVENT_KEY_RELEASED;
599    event.Key.Code = NSCodeToKey([nsEvent keyCode]);
600    AddNSKeyStateToEvent(&event, [nsEvent modifierFlags]);
601    mWindow->pushEvent(event);
602}
603
604// Modifier keys do not trigger keyUp/Down events but only flagsChanged events.
605- (void)flagsChanged:(NSEvent *)nsEvent
606{
607    Event event;
608
609    // Guess if the key has been pressed or released with the change of modifiers
610    // It currently doesn't work when modifiers are unchanged, such as when pressing
611    // both shift keys. GLFW has a solution for this but it requires tracking the
612    // state of the keys. Implementing this is still TODO(cwallez)
613    int modifier = [nsEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
614    if (modifier < mCurrentModifier)
615    {
616        event.Type = Event::EVENT_KEY_RELEASED;
617    }
618    else
619    {
620        event.Type = Event::EVENT_KEY_PRESSED;
621    }
622    mCurrentModifier = modifier;
623
624    event.Key.Code = NSCodeToKey([nsEvent keyCode]);
625    AddNSKeyStateToEvent(&event, [nsEvent modifierFlags]);
626    mWindow->pushEvent(event);
627}
628@end
629
630OSXWindow::OSXWindow() : mWindow(nil), mDelegate(nil), mView(nil) {}
631
632OSXWindow::~OSXWindow()
633{
634    destroy();
635}
636
637bool OSXWindow::initializeImpl(const std::string &name, int width, int height)
638{
639    if (!InitializeAppKit())
640    {
641        return false;
642    }
643
644    unsigned int styleMask = NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask |
645                             NSMiniaturizableWindowMask;
646    mWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, width, height)
647                                          styleMask:styleMask
648                                            backing:NSBackingStoreBuffered
649                                              defer:NO];
650
651    if (mWindow == nil)
652    {
653        return false;
654    }
655
656    mDelegate = [[WindowDelegate alloc] initWithWindow:this];
657    if (mDelegate == nil)
658    {
659        return false;
660    }
661    [mWindow setDelegate:static_cast<id>(mDelegate)];
662
663    mView = [[ContentView alloc] initWithWindow:this];
664    if (mView == nil)
665    {
666        return false;
667    }
668    [mView setWantsLayer:YES];
669
670    // Disable scaling for this view. If scaling is enabled, the metal backend's
671    // frame buffer's size will be this window's size multiplied by contentScale.
672    // It will cause inconsistent testing & example apps' results.
673    mView.layer.contentsScale = 1;
674
675    [mWindow setContentView:mView];
676    [mWindow setTitle:[NSString stringWithUTF8String:name.c_str()]];
677    [mWindow setAcceptsMouseMovedEvents:YES];
678    [mWindow center];
679
680    [NSApp activateIgnoringOtherApps:YES];
681
682    mX      = 0;
683    mY      = 0;
684    mWidth  = width;
685    mHeight = height;
686
687    AllWindows().insert(this);
688    return true;
689}
690
691void OSXWindow::disableErrorMessageDialog() {}
692
693void OSXWindow::destroy()
694{
695    AllWindows().erase(this);
696
697    [mView release];
698    mView = nil;
699    [mDelegate onOSXWindowDeleted];
700    [mDelegate release];
701    mDelegate = nil;
702    // NSWindow won't be completely released unless its content view is set to nil:
703    [mWindow setContentView:nil];
704    [mWindow release];
705    mWindow = nil;
706}
707
708void OSXWindow::resetNativeWindow() {}
709
710EGLNativeWindowType OSXWindow::getNativeWindow() const
711{
712    return [mView layer];
713}
714
715EGLNativeDisplayType OSXWindow::getNativeDisplay() const
716{
717    // TODO(cwallez): implement it once we have defined what EGLNativeDisplayType is
718    return static_cast<EGLNativeDisplayType>(0);
719}
720
721void OSXWindow::messageLoop()
722{
723    @autoreleasepool
724    {
725        while (true)
726        {
727            // TODO(http://anglebug.com/6570): @try/@catch is a workaround for
728            // exceptions being thrown from Cocoa-internal function
729            // NS_setFlushesWithDisplayLink starting in macOS 11.
730            @try
731            {
732                NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask
733                                                    untilDate:[NSDate distantPast]
734                                                       inMode:NSDefaultRunLoopMode
735                                                      dequeue:YES];
736                if (event == nil)
737                {
738                    break;
739                }
740
741                if ([event type] == NSAppKitDefined)
742                {
743                    continue;
744                }
745                [NSApp sendEvent:event];
746            }
747            @catch (NSException *localException)
748            {
749                NSLog(@"*** OSXWindow discarding exception: <%@> %@", [localException name],
750                      [localException reason]);
751            }
752        }
753    }
754}
755
756void OSXWindow::setMousePosition(int x, int y)
757{
758    y = (int)([mWindow frame].size.height) - y - 1;
759    NSPoint screenspace;
760
761#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7
762    screenspace = [mWindow convertBaseToScreen:NSMakePoint(x, y)];
763#else
764    screenspace = [mWindow convertRectToScreen:NSMakeRect(x, y, 0, 0)].origin;
765#endif
766    CGWarpMouseCursorPosition(CGPointMake(screenspace.x, YCoordToFromCG(screenspace.y)));
767}
768
769bool OSXWindow::setOrientation(int width, int height)
770{
771    UNIMPLEMENTED();
772    return false;
773}
774
775bool OSXWindow::setPosition(int x, int y)
776{
777    // Given CG and NS's coordinate system, the "Y" position of a window is the Y coordinate
778    // of the bottom of the window.
779    int newBottom    = (int)([mWindow frame].size.height) + y;
780    NSRect emptyRect = NSMakeRect(x, YCoordToFromCG(newBottom), 0, 0);
781    [mWindow setFrameOrigin:[mWindow frameRectForContentRect:emptyRect].origin];
782    return true;
783}
784
785bool OSXWindow::resize(int width, int height)
786{
787    [mWindow setContentSize:NSMakeSize(width, height)];
788    return true;
789}
790
791void OSXWindow::setVisible(bool isVisible)
792{
793    if (isVisible)
794    {
795        [mWindow makeKeyAndOrderFront:nil];
796    }
797    else
798    {
799        [mWindow orderOut:nil];
800    }
801}
802
803void OSXWindow::signalTestEvent()
804{
805    @autoreleasepool
806    {
807        NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined
808                                            location:NSMakePoint(0, 0)
809                                       modifierFlags:0
810                                           timestamp:0.0
811                                        windowNumber:[mWindow windowNumber]
812                                             context:nil
813                                             subtype:0
814                                               data1:0
815                                               data2:0];
816        [NSApp postEvent:event atStart:YES];
817    }
818}
819
820NSWindow *OSXWindow::getNSWindow() const
821{
822    return mWindow;
823}
824
825// static
826OSWindow *OSWindow::New()
827{
828    return new OSXWindow;
829}
830