bgfx/examples/common/entry/entry_osx.mm
Richard Gale 631103bfa8 Adds suspend event mapped to application lifecycle
On Android WillSuspend and WillResume maps to focus and DidSuspend and
DidResume maps to onPause and onResume.

On OSX WillSuspend and DidSuspend maps to resign key, WillResume and
DidResumg maps to make key.
2015-08-24 19:12:01 -07:00

762 lines
18 KiB
Text

/*
* Copyright 2011-2015 Branimir Karadzic. All rights reserved.
* License: http://www.opensource.org/licenses/BSD-2-Clause
*/
#include "entry_p.h"
#if ENTRY_CONFIG_USE_NATIVE && BX_PLATFORM_OSX
#import <Cocoa/Cocoa.h>
#include <bgfxplatform.h>
#include <bx/uint32_t.h>
#include <bx/thread.h>
#include <bx/os.h>
#include <bx/handlealloc.h>
@interface AppDelegate : NSObject<NSApplicationDelegate>
{
bool terminated;
}
+ (AppDelegate *)sharedDelegate;
- (id)init;
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
- (bool)applicationHasTerminated;
@end
@interface Window : NSObject<NSWindowDelegate>
{
uint32_t windowCount;
}
+ (Window*)sharedDelegate;
- (id)init;
- (void)windowCreated:(NSWindow*)window;
- (void)windowWillClose:(NSNotification*)notification;
- (BOOL)windowShouldClose:(NSWindow*)window;
- (void)windowDidResize:(NSNotification*)notification;
- (void)windowDidBecomeKey:(NSNotification *)notification;
- (void)windowDidResignKey:(NSNotification *)notification;
@end
namespace entry
{
static WindowHandle s_defaultWindow = { 0 }; // TODO: Add support for more windows
static uint8_t s_translateKey[256];
struct MainThreadEntry
{
int m_argc;
char** m_argv;
static int32_t threadFunc(void* _userData)
{
CFBundleRef mainBundle = CFBundleGetMainBundle();
if ( mainBundle != nil )
{
CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(mainBundle);
if ( resourcesURL != nil )
{
char path[PATH_MAX];
if (CFURLGetFileSystemRepresentation(resourcesURL, TRUE, (UInt8 *)path, PATH_MAX) )
{
chdir(path);
}
CFRelease(resourcesURL);
}
}
MainThreadEntry* self = (MainThreadEntry*)_userData;
return main(self->m_argc, self->m_argv);
}
};
struct Context
{
Context()
: m_scrollf(0.0f)
, m_mx(0)
, m_my(0)
, m_scroll(0)
, m_style(0)
, m_exit(false)
, m_fullscreen(false)
{
s_translateKey[27] = Key::Esc;
s_translateKey[uint8_t('\n')] = Key::Return;
s_translateKey[uint8_t('\t')] = Key::Tab;
s_translateKey[127] = Key::Backspace;
s_translateKey[uint8_t(' ')] = Key::Space;
s_translateKey[uint8_t('+')] =
s_translateKey[uint8_t('=')] = Key::Plus;
s_translateKey[uint8_t('_')] =
s_translateKey[uint8_t('-')] = Key::Minus;
s_translateKey[uint8_t('~')] =
s_translateKey[uint8_t('`')] = Key::Tilde;
s_translateKey[uint8_t(':')] =
s_translateKey[uint8_t(';')] = Key::Semicolon;
s_translateKey[uint8_t('"')] =
s_translateKey[uint8_t('\'')] = Key::Quote;
s_translateKey[uint8_t('{')] =
s_translateKey[uint8_t('[')] = Key::LeftBracket;
s_translateKey[uint8_t('}')] =
s_translateKey[uint8_t(']')] = Key::RightBracket;
s_translateKey[uint8_t('<')] =
s_translateKey[uint8_t(',')] = Key::Comma;
s_translateKey[uint8_t('>')] =
s_translateKey[uint8_t('.')] = Key::Period;
s_translateKey[uint8_t('?')] =
s_translateKey[uint8_t('/')] = Key::Slash;
s_translateKey[uint8_t('|')] =
s_translateKey[uint8_t('\\')] = Key::Backslash;
s_translateKey[uint8_t('0')] = Key::Key0;
s_translateKey[uint8_t('1')] = Key::Key1;
s_translateKey[uint8_t('2')] = Key::Key2;
s_translateKey[uint8_t('3')] = Key::Key3;
s_translateKey[uint8_t('4')] = Key::Key4;
s_translateKey[uint8_t('5')] = Key::Key5;
s_translateKey[uint8_t('6')] = Key::Key6;
s_translateKey[uint8_t('7')] = Key::Key7;
s_translateKey[uint8_t('8')] = Key::Key8;
s_translateKey[uint8_t('9')] = Key::Key9;
for (char ch = 'a'; ch <= 'z'; ++ch)
{
s_translateKey[uint8_t(ch)] =
s_translateKey[uint8_t(ch - ' ')] = Key::KeyA + (ch - 'a');
}
}
NSEvent* waitEvent()
{
return [NSApp
nextEventMatchingMask:NSAnyEventMask
untilDate:[NSDate distantFuture] // wait for event
inMode:NSDefaultRunLoopMode
dequeue:YES
];
}
NSEvent* peekEvent()
{
return [NSApp
nextEventMatchingMask:NSAnyEventMask
untilDate:[NSDate distantPast] // do not wait for event
inMode:NSDefaultRunLoopMode
dequeue:YES
];
}
void getMousePos(int* outX, int* outY)
{
WindowHandle handle = { 0 };
NSWindow* window = m_window[handle.idx];
NSRect originalFrame = [window frame];
NSPoint location = [window mouseLocationOutsideOfEventStream];
NSRect adjustFrame = [window contentRectForFrameRect: originalFrame];
int x = location.x;
int y = (int)adjustFrame.size.height - (int)location.y;
// clamp within the range of the window
if (x < 0) x = 0;
if (y < 0) y = 0;
if (x > (int)adjustFrame.size.width) x = (int)adjustFrame.size.width;
if (y > (int)adjustFrame.size.height) y = (int)adjustFrame.size.height;
*outX = x;
*outY = y;
}
uint8_t translateModifiers(int flags)
{
uint8_t mask = 0;
if (flags & NSShiftKeyMask)
mask |= Modifier::LeftShift | Modifier::RightShift;
if (flags & NSAlternateKeyMask)
mask |= Modifier::LeftAlt | Modifier::RightAlt;
if (flags & NSControlKeyMask)
mask |= Modifier::LeftCtrl | Modifier::RightCtrl;
if (flags & NSCommandKeyMask)
mask |= Modifier::LeftMeta | Modifier::RightMeta;
return mask;
}
Key::Enum handleKeyEvent(NSEvent* event, uint8_t* specialKeys, uint8_t* _pressedChar)
{
NSString* key = [event charactersIgnoringModifiers];
unichar keyChar = 0;
if ([key length] == 0)
{
return Key::None;
}
keyChar = [key characterAtIndex:0];
*_pressedChar = (uint8_t)keyChar;
int keyCode = keyChar;
*specialKeys = translateModifiers([event modifierFlags]);
// if this is a unhandled key just return None
if (keyCode < 256)
{
return (Key::Enum)s_translateKey[keyCode];
}
switch (keyCode)
{
case NSF1FunctionKey: return Key::F1;
case NSF2FunctionKey: return Key::F2;
case NSF3FunctionKey: return Key::F3;
case NSF4FunctionKey: return Key::F4;
case NSF5FunctionKey: return Key::F5;
case NSF6FunctionKey: return Key::F6;
case NSF7FunctionKey: return Key::F7;
case NSF8FunctionKey: return Key::F8;
case NSF9FunctionKey: return Key::F9;
case NSF10FunctionKey: return Key::F10;
case NSF11FunctionKey: return Key::F11;
case NSF12FunctionKey: return Key::F12;
case NSLeftArrowFunctionKey: return Key::Left;
case NSRightArrowFunctionKey: return Key::Right;
case NSUpArrowFunctionKey: return Key::Up;
case NSDownArrowFunctionKey: return Key::Down;
case NSPageUpFunctionKey: return Key::PageUp;
case NSPageDownFunctionKey: return Key::PageDown;
case NSHomeFunctionKey: return Key::Home;
case NSEndFunctionKey: return Key::End;
case NSPrintScreenFunctionKey: return Key::Print;
}
return Key::None;
}
bool dispatchEvent(NSEvent* event)
{
if (event)
{
NSEventType eventType = [event type];
switch (eventType)
{
case NSMouseMoved:
case NSLeftMouseDragged:
case NSRightMouseDragged:
case NSOtherMouseDragged:
{
getMousePos(&m_mx, &m_my);
m_eventQueue.postMouseEvent(s_defaultWindow, m_mx, m_my, m_scroll);
break;
}
case NSLeftMouseDown:
{
// TODO: remove!
// Command + Left Mouse Button acts as middle! This just a temporary solution!
// This is becase the average OSX user doesn't have middle mouse click.
MouseButton::Enum mb = ([event modifierFlags] & NSCommandKeyMask) ? MouseButton::Middle : MouseButton::Left;
m_eventQueue.postMouseEvent(s_defaultWindow, m_mx, m_my, m_scroll, mb, true);
break;
}
case NSLeftMouseUp:
{
m_eventQueue.postMouseEvent(s_defaultWindow, m_mx, m_my, m_scroll, MouseButton::Left, false);
m_eventQueue.postMouseEvent(s_defaultWindow, m_mx, m_my, m_scroll, MouseButton::Middle, false); // TODO: remove!
break;
}
case NSRightMouseDown:
{
m_eventQueue.postMouseEvent(s_defaultWindow, m_mx, m_my, m_scroll, MouseButton::Right, true);
break;
}
case NSRightMouseUp:
{
m_eventQueue.postMouseEvent(s_defaultWindow, m_mx, m_my, m_scroll, MouseButton::Right, false);
break;
}
case NSOtherMouseDown:
{
m_eventQueue.postMouseEvent(s_defaultWindow, m_mx, m_my, m_scroll, MouseButton::Middle, true);
break;
}
case NSOtherMouseUp:
{
m_eventQueue.postMouseEvent(s_defaultWindow, m_mx, m_my, m_scroll, MouseButton::Middle, false);
break;
}
case NSScrollWheel:
{
m_scrollf += [event deltaY];
m_scroll = (int32_t)m_scrollf;
m_eventQueue.postMouseEvent(s_defaultWindow, m_mx, m_my, m_scroll);
break;
}
case NSKeyDown:
{
uint8_t modifiers = 0;
uint8_t pressedChar[4];
Key::Enum key = handleKeyEvent(event, &modifiers, &pressedChar[0]);
// Returning false means that we take care of the key (instead of the default behavior)
if (key != Key::None)
{
if (key == Key::KeyQ && (modifiers & Modifier::RightMeta) )
{
m_eventQueue.postExitEvent();
}
else
{
enum { ShiftMask = Modifier::LeftShift|Modifier::RightShift };
m_eventQueue.postCharEvent(s_defaultWindow, 1, pressedChar);
m_eventQueue.postKeyEvent(s_defaultWindow, key, modifiers, true);
return false;
}
}
break;
}
case NSKeyUp:
{
uint8_t modifiers = 0;
uint8_t pressedChar[4];
Key::Enum key = handleKeyEvent(event, &modifiers, &pressedChar[0]);
BX_UNUSED(pressedChar);
if (key != Key::None)
{
m_eventQueue.postKeyEvent(s_defaultWindow, key, modifiers, false);
return false;
}
break;
}
}
[NSApp sendEvent:event];
[NSApp updateWindows];
return true;
}
return false;
}
void windowDidResize()
{
WindowHandle handle = { 0 };
NSWindow* window = m_window[handle.idx];
NSRect originalFrame = [window frame];
NSRect rect = [window contentRectForFrameRect: originalFrame];
uint32_t width = uint32_t(rect.size.width);
uint32_t height = uint32_t(rect.size.height);
m_eventQueue.postSizeEvent(handle, width, height);
// Make sure mouse button state is 'up' after resize.
m_eventQueue.postMouseEvent(s_defaultWindow, m_mx, m_my, m_scroll, MouseButton::Left, false);
m_eventQueue.postMouseEvent(s_defaultWindow, m_mx, m_my, m_scroll, MouseButton::Right, false);
}
void windowDidBecomeKey()
{
m_eventQueue.postSuspendEvent(s_defaultWindow, Suspend::WillResume);
m_eventQueue.postSuspendEvent(s_defaultWindow, Suspend::DidResume);
}
void windowDidResignKey()
{
m_eventQueue.postSuspendEvent(s_defaultWindow, Suspend::WillSuspend);
m_eventQueue.postSuspendEvent(s_defaultWindow, Suspend::DidSuspend);
}
int32_t run(int _argc, char** _argv)
{
[NSApplication sharedApplication];
id dg = [AppDelegate sharedDelegate];
[NSApp setDelegate:dg];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp activateIgnoringOtherApps:YES];
[NSApp finishLaunching];
[[NSNotificationCenter defaultCenter]
postNotificationName:NSApplicationWillFinishLaunchingNotification
object:NSApp];
[[NSNotificationCenter defaultCenter]
postNotificationName:NSApplicationDidFinishLaunchingNotification
object:NSApp];
id quitMenuItem = [NSMenuItem new];
[quitMenuItem
initWithTitle:@"Quit"
action:@selector(terminate:)
keyEquivalent:@"q"];
id appMenu = [NSMenu new];
[appMenu addItem:quitMenuItem];
id appMenuItem = [NSMenuItem new];
[appMenuItem setSubmenu:appMenu];
id menubar = [[NSMenu new] autorelease];
[menubar addItem:appMenuItem];
[NSApp setMainMenu:menubar];
m_style = 0
| NSTitledWindowMask
| NSClosableWindowMask
| NSMiniaturizableWindowMask
| NSResizableWindowMask
;
NSRect screenRect = [[NSScreen mainScreen] frame];
const float centerX = (screenRect.size.width - (float)ENTRY_DEFAULT_WIDTH )*0.5f;
const float centerY = (screenRect.size.height - (float)ENTRY_DEFAULT_HEIGHT)*0.5f;
m_windowAlloc.alloc();
NSRect rect = NSMakeRect(centerX, centerY, (float)ENTRY_DEFAULT_WIDTH, (float)ENTRY_DEFAULT_HEIGHT);
NSWindow* window = [[NSWindow alloc]
initWithContentRect:rect
styleMask:m_style
backing:NSBackingStoreBuffered defer:NO
];
NSString* appName = [[NSProcessInfo processInfo] processName];
[window setTitle:appName];
[window makeKeyAndOrderFront:window];
[window setAcceptsMouseMovedEvents:YES];
[window setBackgroundColor:[NSColor blackColor]];
[[Window sharedDelegate] windowCreated:window];
m_window[0] = window;
m_windowFrame = [window frame];
bgfx::osxSetNSWindow(window);
MainThreadEntry mte;
mte.m_argc = _argc;
mte.m_argv = _argv;
bx::Thread thread;
thread.init(mte.threadFunc, &mte);
while (!(m_exit = [dg applicationHasTerminated]) )
{
if (bgfx::RenderFrame::Exiting == bgfx::renderFrame() )
{
break;
}
while (dispatchEvent(peekEvent() ) )
{
}
}
m_eventQueue.postExitEvent();
while (bgfx::RenderFrame::NoContext != bgfx::renderFrame() ) {};
thread.shutdown();
return 0;
}
bool isValid(WindowHandle _handle)
{
return m_windowAlloc.isValid(_handle.idx);
}
EventQueue m_eventQueue;
bx::HandleAllocT<ENTRY_CONFIG_MAX_WINDOWS> m_windowAlloc;
NSWindow* m_window[ENTRY_CONFIG_MAX_WINDOWS];
NSRect m_windowFrame;
float m_scrollf;
int32_t m_mx;
int32_t m_my;
int32_t m_scroll;
int32_t m_style;
bool m_exit;
bool m_fullscreen;
};
static Context s_ctx;
const Event* poll()
{
return s_ctx.m_eventQueue.poll();
}
const Event* poll(WindowHandle _handle)
{
return s_ctx.m_eventQueue.poll(_handle);
}
void release(const Event* _event)
{
s_ctx.m_eventQueue.release(_event);
}
WindowHandle createWindow(int32_t _x, int32_t _y, uint32_t _width, uint32_t _height, uint32_t _flags, const char* _title)
{
BX_UNUSED(_x, _y, _width, _height, _flags, _title);
WindowHandle handle = { UINT16_MAX };
return handle;
}
void destroyWindow(WindowHandle _handle)
{
if (s_ctx.isValid(_handle) )
{
dispatch_async(dispatch_get_main_queue()
, ^{
[s_ctx.m_window[_handle.idx] performClose: nil];
});
}
}
void setWindowPos(WindowHandle _handle, int32_t _x, int32_t _y)
{
if (s_ctx.isValid(_handle) )
{
NSWindow* window = s_ctx.m_window[_handle.idx];
NSScreen* screen = [window screen];
NSRect screenRect = [screen frame];
CGFloat menuBarHeight = [[[NSApplication sharedApplication] mainMenu] menuBarHeight];
NSPoint position = { float(_x), screenRect.size.height - menuBarHeight - float(_y) };
dispatch_async(dispatch_get_main_queue()
, ^{
[window setFrameTopLeftPoint: position];
});
}
}
void setWindowSize(WindowHandle _handle, uint32_t _width, uint32_t _height)
{
if (s_ctx.isValid(_handle) )
{
NSSize size = { float(_width), float(_height) };
dispatch_async(dispatch_get_main_queue()
, ^{
[s_ctx.m_window[_handle.idx] setContentSize: size];
});
}
}
void setWindowTitle(WindowHandle _handle, const char* _title)
{
if (s_ctx.isValid(_handle) )
{
NSString* title = [[NSString alloc] initWithCString:_title encoding:1];
dispatch_async(dispatch_get_main_queue()
, ^{
[s_ctx.m_window[_handle.idx] setTitle: title];
});
[title release];
}
}
void toggleWindowFrame(WindowHandle _handle)
{
if (s_ctx.isValid(_handle) )
{
s_ctx.m_style ^= NSTitledWindowMask;
dispatch_async(dispatch_get_main_queue()
, ^{
[s_ctx.m_window[_handle.idx] setStyleMask: s_ctx.m_style];
});
}
}
void toggleFullscreen(WindowHandle _handle)
{
if (s_ctx.isValid(_handle) )
{
NSWindow* window = s_ctx.m_window[_handle.idx];
NSScreen* screen = [window screen];
NSRect screenRect = [screen frame];
if (!s_ctx.m_fullscreen)
{
s_ctx.m_style &= ~NSTitledWindowMask;
dispatch_async(dispatch_get_main_queue()
, ^{
[NSMenu setMenuBarVisible: false];
[window setStyleMask: s_ctx.m_style];
[window setFrame:screenRect display:YES];
});
s_ctx.m_fullscreen = true;
}
else
{
s_ctx.m_style |= NSTitledWindowMask;
dispatch_async(dispatch_get_main_queue()
, ^{
[NSMenu setMenuBarVisible: true];
[window setStyleMask: s_ctx.m_style];
[window setFrame:s_ctx.m_windowFrame display:YES];
});
s_ctx.m_fullscreen = false;
}
}
}
void setMouseLock(WindowHandle _handle, bool _lock)
{
BX_UNUSED(_handle, _lock);
}
} // namespace entry
@implementation AppDelegate
+ (AppDelegate *)sharedDelegate
{
static id delegate = [AppDelegate new];
return delegate;
}
- (id)init
{
self = [super init];
if (nil == self)
{
return nil;
}
self->terminated = false;
return self;
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
BX_UNUSED(sender);
self->terminated = true;
return NSTerminateCancel;
}
- (bool)applicationHasTerminated
{
return self->terminated;
}
@end
@implementation Window
+ (Window*)sharedDelegate
{
static id windowDelegate = [Window new];
return windowDelegate;
}
- (id)init
{
self = [super init];
if (nil == self)
{
return nil;
}
self->windowCount = 0;
return self;
}
- (void)windowCreated:(NSWindow*)window
{
assert(window);
[window setDelegate:self];
assert(self->windowCount < ~0u);
self->windowCount += 1;
}
- (void)windowWillClose:(NSNotification*)notification
{
BX_UNUSED(notification);
}
- (BOOL)windowShouldClose:(NSWindow*)window
{
assert(window);
[window setDelegate:nil];
assert(self->windowCount);
self->windowCount -= 1;
if (self->windowCount == 0)
{
[NSApp terminate:self];
return false;
}
return true;
}
- (void)windowDidResize:(NSNotification*)notification
{
BX_UNUSED(notification);
using namespace entry;
s_ctx.windowDidResize();
}
- (void)windowDidBecomeKey:(NSNotification*)notification
{
BX_UNUSED(notification);
using namespace entry;
s_ctx.windowDidBecomeKey();
}
- (void)windowDidResignKey:(NSNotification*)notification
{
BX_UNUSED(notification);
using namespace entry;
s_ctx.windowDidResignKey();
}
@end
int main(int _argc, char** _argv)
{
using namespace entry;
return s_ctx.run(_argc, _argv);
}
#endif // BX_PLATFORM_OSX