orca/src/app/osx_app.m

2372 lines
67 KiB
Objective-C

//*****************************************************************
//
// $file: osx_app.m $
// $author: Martin Fouilleul $
// $date: 16/05/2020 $
// $revision: $
// $note: (C) 2020 by Martin Fouilleul - all rights reserved $
//
//*****************************************************************
#import <QuartzCore/QuartzCore.h> //CATransaction
#include <stdlib.h> // malloc/free
#include "graphics/graphics_surface.h"
#include "lists.h"
#include "macros.h"
#include "memory.h"
#include "platform_clock.h"
#include "platform_debug.h"
#include "ringbuffer.h"
#include "app.c"
//--------------------------------------------------------------------
// mp window struct and utility functions
//--------------------------------------------------------------------
static u32 oc_osx_get_window_style_mask(oc_window_style style)
{
u32 mask = 0;
if(style & OC_WINDOW_STYLE_NO_TITLE)
{
mask = NSWindowStyleMaskBorderless;
}
else
{
mask = NSWindowStyleMaskTitled;
}
if(!(style & OC_WINDOW_STYLE_FIXED_SIZE))
{
mask |= NSWindowStyleMaskResizable;
}
if(!(style & OC_WINDOW_STYLE_NO_CLOSE))
{
mask |= NSWindowStyleMaskClosable;
}
if(!(style & OC_WINDOW_STYLE_NO_MINIFY))
{
mask |= NSWindowStyleMaskMiniaturizable;
}
return (mask);
}
//---------------------------------------------------------------
static void oc_init_osx_keys()
{
memset(oc_appData.keyCodes, OC_KEY_UNKNOWN, 256 * sizeof(int));
oc_appData.keyCodes[0x1D] = OC_KEY_0;
oc_appData.keyCodes[0x12] = OC_KEY_1;
oc_appData.keyCodes[0x13] = OC_KEY_2;
oc_appData.keyCodes[0x14] = OC_KEY_3;
oc_appData.keyCodes[0x15] = OC_KEY_4;
oc_appData.keyCodes[0x17] = OC_KEY_5;
oc_appData.keyCodes[0x16] = OC_KEY_6;
oc_appData.keyCodes[0x1A] = OC_KEY_7;
oc_appData.keyCodes[0x1C] = OC_KEY_8;
oc_appData.keyCodes[0x19] = OC_KEY_9;
oc_appData.keyCodes[0x00] = OC_KEY_A;
oc_appData.keyCodes[0x0B] = OC_KEY_B;
oc_appData.keyCodes[0x08] = OC_KEY_C;
oc_appData.keyCodes[0x02] = OC_KEY_D;
oc_appData.keyCodes[0x0E] = OC_KEY_E;
oc_appData.keyCodes[0x03] = OC_KEY_F;
oc_appData.keyCodes[0x05] = OC_KEY_G;
oc_appData.keyCodes[0x04] = OC_KEY_H;
oc_appData.keyCodes[0x22] = OC_KEY_I;
oc_appData.keyCodes[0x26] = OC_KEY_J;
oc_appData.keyCodes[0x28] = OC_KEY_K;
oc_appData.keyCodes[0x25] = OC_KEY_L;
oc_appData.keyCodes[0x2E] = OC_KEY_M;
oc_appData.keyCodes[0x2D] = OC_KEY_N;
oc_appData.keyCodes[0x1F] = OC_KEY_O;
oc_appData.keyCodes[0x23] = OC_KEY_P;
oc_appData.keyCodes[0x0C] = OC_KEY_Q;
oc_appData.keyCodes[0x0F] = OC_KEY_R;
oc_appData.keyCodes[0x01] = OC_KEY_S;
oc_appData.keyCodes[0x11] = OC_KEY_T;
oc_appData.keyCodes[0x20] = OC_KEY_U;
oc_appData.keyCodes[0x09] = OC_KEY_V;
oc_appData.keyCodes[0x0D] = OC_KEY_W;
oc_appData.keyCodes[0x07] = OC_KEY_X;
oc_appData.keyCodes[0x10] = OC_KEY_Y;
oc_appData.keyCodes[0x06] = OC_KEY_Z;
oc_appData.keyCodes[0x27] = OC_KEY_APOSTROPHE;
oc_appData.keyCodes[0x2A] = OC_KEY_BACKSLASH;
oc_appData.keyCodes[0x2B] = OC_KEY_COMMA;
oc_appData.keyCodes[0x18] = OC_KEY_EQUAL;
oc_appData.keyCodes[0x32] = OC_KEY_GRAVE_ACCENT;
oc_appData.keyCodes[0x21] = OC_KEY_LEFT_BRACKET;
oc_appData.keyCodes[0x1B] = OC_KEY_MINUS;
oc_appData.keyCodes[0x2F] = OC_KEY_PERIOD;
oc_appData.keyCodes[0x1E] = OC_KEY_RIGHT_BRACKET;
oc_appData.keyCodes[0x29] = OC_KEY_SEMICOLON;
oc_appData.keyCodes[0x2C] = OC_KEY_SLASH;
oc_appData.keyCodes[0x0A] = OC_KEY_WORLD_1;
oc_appData.keyCodes[0x33] = OC_KEY_BACKSPACE;
oc_appData.keyCodes[0x39] = OC_KEY_CAPS_LOCK;
oc_appData.keyCodes[0x75] = OC_KEY_DELETE;
oc_appData.keyCodes[0x7D] = OC_KEY_DOWN;
oc_appData.keyCodes[0x77] = OC_KEY_END;
oc_appData.keyCodes[0x24] = OC_KEY_ENTER;
oc_appData.keyCodes[0x35] = OC_KEY_ESCAPE;
oc_appData.keyCodes[0x7A] = OC_KEY_F1;
oc_appData.keyCodes[0x78] = OC_KEY_F2;
oc_appData.keyCodes[0x63] = OC_KEY_F3;
oc_appData.keyCodes[0x76] = OC_KEY_F4;
oc_appData.keyCodes[0x60] = OC_KEY_F5;
oc_appData.keyCodes[0x61] = OC_KEY_F6;
oc_appData.keyCodes[0x62] = OC_KEY_F7;
oc_appData.keyCodes[0x64] = OC_KEY_F8;
oc_appData.keyCodes[0x65] = OC_KEY_F9;
oc_appData.keyCodes[0x6D] = OC_KEY_F10;
oc_appData.keyCodes[0x67] = OC_KEY_F11;
oc_appData.keyCodes[0x6F] = OC_KEY_F12;
oc_appData.keyCodes[0x69] = OC_KEY_F13;
oc_appData.keyCodes[0x6B] = OC_KEY_F14;
oc_appData.keyCodes[0x71] = OC_KEY_F15;
oc_appData.keyCodes[0x6A] = OC_KEY_F16;
oc_appData.keyCodes[0x40] = OC_KEY_F17;
oc_appData.keyCodes[0x4F] = OC_KEY_F18;
oc_appData.keyCodes[0x50] = OC_KEY_F19;
oc_appData.keyCodes[0x5A] = OC_KEY_F20;
oc_appData.keyCodes[0x73] = OC_KEY_HOME;
oc_appData.keyCodes[0x72] = OC_KEY_INSERT;
oc_appData.keyCodes[0x7B] = OC_KEY_LEFT;
oc_appData.keyCodes[0x3A] = OC_KEY_LEFT_ALT;
oc_appData.keyCodes[0x3B] = OC_KEY_LEFT_CONTROL;
oc_appData.keyCodes[0x38] = OC_KEY_LEFT_SHIFT;
oc_appData.keyCodes[0x37] = OC_KEY_LEFT_SUPER;
oc_appData.keyCodes[0x6E] = OC_KEY_MENU;
oc_appData.keyCodes[0x47] = OC_KEY_NUM_LOCK;
oc_appData.keyCodes[0x79] = OC_KEY_PAGE_DOWN;
oc_appData.keyCodes[0x74] = OC_KEY_PAGE_UP;
oc_appData.keyCodes[0x7C] = OC_KEY_RIGHT;
oc_appData.keyCodes[0x3D] = OC_KEY_RIGHT_ALT;
oc_appData.keyCodes[0x3E] = OC_KEY_RIGHT_CONTROL;
oc_appData.keyCodes[0x3C] = OC_KEY_RIGHT_SHIFT;
oc_appData.keyCodes[0x36] = OC_KEY_RIGHT_SUPER;
oc_appData.keyCodes[0x31] = OC_KEY_SPACE;
oc_appData.keyCodes[0x30] = OC_KEY_TAB;
oc_appData.keyCodes[0x7E] = OC_KEY_UP;
oc_appData.keyCodes[0x52] = OC_KEY_KP_0;
oc_appData.keyCodes[0x53] = OC_KEY_KP_1;
oc_appData.keyCodes[0x54] = OC_KEY_KP_2;
oc_appData.keyCodes[0x55] = OC_KEY_KP_3;
oc_appData.keyCodes[0x56] = OC_KEY_KP_4;
oc_appData.keyCodes[0x57] = OC_KEY_KP_5;
oc_appData.keyCodes[0x58] = OC_KEY_KP_6;
oc_appData.keyCodes[0x59] = OC_KEY_KP_7;
oc_appData.keyCodes[0x5B] = OC_KEY_KP_8;
oc_appData.keyCodes[0x5C] = OC_KEY_KP_9;
oc_appData.keyCodes[0x45] = OC_KEY_KP_ADD;
oc_appData.keyCodes[0x41] = OC_KEY_KP_DECIMAL;
oc_appData.keyCodes[0x4B] = OC_KEY_KP_DIVIDE;
oc_appData.keyCodes[0x4C] = OC_KEY_KP_ENTER;
oc_appData.keyCodes[0x51] = OC_KEY_KP_EQUAL;
oc_appData.keyCodes[0x43] = OC_KEY_KP_MULTIPLY;
oc_appData.keyCodes[0x4E] = OC_KEY_KP_SUBTRACT;
memset(oc_appData.nativeKeys, 0, sizeof(int) * OC_KEY_COUNT);
for(int nativeKey = 0; nativeKey < 256; nativeKey++)
{
oc_key_code mpKey = oc_appData.keyCodes[nativeKey];
if(mpKey)
{
oc_appData.nativeKeys[mpKey] = nativeKey;
}
}
}
static int oc_convert_osx_key(unsigned short nsCode)
{
if(nsCode >= 265)
{
return (OC_KEY_UNKNOWN);
}
else
{
return (oc_appData.keyCodes[nsCode]);
}
}
static oc_keymod_flags oc_convert_osx_mods(NSUInteger nsFlags)
{
oc_keymod_flags mods = OC_KEYMOD_NONE;
if(nsFlags & NSEventModifierFlagShift)
{
mods |= OC_KEYMOD_SHIFT;
}
if(nsFlags & NSEventModifierFlagControl)
{
mods |= OC_KEYMOD_CTRL;
}
if(nsFlags & NSEventModifierFlagOption)
{
mods |= OC_KEYMOD_ALT;
}
if(nsFlags & NSEventModifierFlagCommand)
{
mods |= OC_KEYMOD_CMD;
mods |= OC_KEYMOD_MAIN_MODIFIER;
}
return (mods);
}
static void oc_update_keyboard_layout()
{
if(oc_appData.osx.kbLayoutInputSource)
{
CFRelease(oc_appData.osx.kbLayoutInputSource);
oc_appData.osx.kbLayoutInputSource = 0;
oc_appData.osx.kbLayoutUnicodeData = nil;
}
oc_appData.osx.kbLayoutInputSource = TISCopyCurrentKeyboardLayoutInputSource();
if(!oc_appData.osx.kbLayoutInputSource)
{
oc_log_error("Failed to load keyboard layout input source");
}
oc_appData.osx.kbLayoutUnicodeData = TISGetInputSourceProperty(oc_appData.osx.kbLayoutInputSource,
kTISPropertyUnicodeKeyLayoutData);
if(!oc_appData.osx.kbLayoutUnicodeData)
{
oc_log_error("Failed to load keyboard layout unicode data");
}
memset(oc_appData.keyLabels, 0, sizeof(oc_key_utf8) * OC_KEY_COUNT);
for(int key = 0; key < OC_KEY_COUNT; key++)
{
//TODO: check that the key is printable
int nativeKey = oc_appData.nativeKeys[key];
UInt32 deadKeyState = 0;
UniChar characters[4];
UniCharCount characterCount = 0;
if(UCKeyTranslate((UCKeyboardLayout*)[(NSData*)oc_appData.osx.kbLayoutUnicodeData bytes],
nativeKey,
kUCKeyActionDisplay,
0,
LMGetKbdType(),
kUCKeyTranslateNoDeadKeysBit,
&deadKeyState,
sizeof(characters) / sizeof(UniChar),
&characterCount,
characters)
!= noErr)
{
oc_appData.keyLabels[key].labelLen = 0;
}
else
{
NSString* nsString = [[NSString alloc] initWithCharacters:characters length:characterCount];
const char* cstring = [nsString UTF8String];
u32 len = strlen(cstring);
oc_appData.keyLabels[key].labelLen = oc_min(len, 8);
memcpy(oc_appData.keyLabels[key].label, cstring, oc_appData.keyLabels[key].labelLen);
}
}
}
oc_str8 oc_key_to_label(oc_key_code key)
{
oc_key_utf8* keyInfo = &(oc_appData.keyLabels[key]);
oc_str8 label = oc_str8_from_buffer(keyInfo->labelLen, keyInfo->label);
return (label);
}
oc_key_code oc_label_to_key(oc_str8 label)
{
oc_key_code res = OC_KEY_UNKNOWN;
for(int key = 0; key < OC_KEY_COUNT; key++)
{
oc_str8 keyLabel = oc_key_to_label(key);
if(keyLabel.len == label.len
&& !strncmp(keyLabel.ptr, label.ptr, label.len))
{
res = key;
break;
}
}
return (res);
}
@interface OCWindow : NSWindow
{
oc_window_data* mpWindow;
}
- (id)initWithWindowData:(oc_window_data*)window contentRect:(NSRect)rect styleMask:(uint32)style;
@end
@interface OCKeyboardLayoutListener : NSObject
@end
@implementation OCKeyboardLayoutListener
- (void)selectedKeyboardInputSourceChanged:(NSObject*)object
{
oc_update_keyboard_layout();
}
@end
void oc_install_keyboard_layout_listener()
{
oc_appData.osx.kbLayoutListener = [[OCKeyboardLayoutListener alloc] init];
[[NSDistributedNotificationCenter defaultCenter]
addObserver:oc_appData.osx.kbLayoutListener
selector:@selector(selectedKeyboardInputSourceChanged:)
name:(__bridge NSString*)kTISNotifySelectedKeyboardInputSourceChanged
object:nil];
}
//---------------------------------------------------------------
// Application and app delegate
//---------------------------------------------------------------
@interface OCApplication : NSApplication
@end
@implementation OCApplication
- (void)noOpThread:(id)object
{
}
//This is necessary in order to receive keyUp events when we have a key combination with Cmd.
- (void)sendEvent:(NSEvent*)event
{
if([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand))
[[self keyWindow] sendEvent:event];
else
[super sendEvent:event];
}
@end
@interface OCAppDelegate : NSObject <NSApplicationDelegate>
- (id)init;
@end
@implementation OCAppDelegate
- (id)init
{
self = [super init];
[[NSAppleEventManager sharedAppleEventManager] setEventHandler:self
andSelector:@selector(handleAppleEvent:withReplyEvent:)
forEventClass:kInternetEventClass
andEventID:kAEGetURL];
return (self);
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender
{
//NOTE: We set shouldQuit to true and send a Quit event
// We then return a value to cancel the direct termination because we still
// want to execte the code after oc_event_loop(). If the user didn't set shouldQuit to
// false, oc_event_loop() will exit, and the user can execute any cleanup needed and
// exit the program.
oc_appData.shouldQuit = true;
oc_event event = {};
event.type = OC_EVENT_QUIT;
oc_queue_event(&event);
return (NSTerminateCancel);
}
- (void)applicationWillFinishLaunching:(NSNotification*)notification
{
@autoreleasepool
{
//NOTE(martin): add a menu for quit, and a corresponding key equivalent.
// this allows to quit the application when there is no window
// left to catch our Cmd-Q key equivalent
NSMenu* bar = [[NSMenu alloc] init];
[NSApp setMainMenu:bar];
NSMenuItem* appMenuItem =
[bar addItemWithTitle:@""
action:NULL
keyEquivalent:@""];
NSMenu* appMenu = [[NSMenu alloc] init];
[appMenuItem setSubmenu:appMenu];
[appMenu addItemWithTitle:@"Quit"
action:@selector(terminate:)
keyEquivalent:@"q"];
}
}
- (void)timerElapsed:(NSTimer*)timer
{
oc_event event = {};
event.type = OC_EVENT_FRAME;
oc_queue_event(&event);
}
- (void)applicationDidFinishLaunching:(NSNotification*)notification
{
@autoreleasepool
{
//WARN(martin): the order of these calls seem to matter a lot for properly showing the menu bar
// with other orderings, the menu doesn't display before the application is put out of
// focus and on focus again... This is flaky undocumented behaviour, so although it is
// fixed by the current ordering, we expect the problem to show up again in future
// versions of macOS.
//NOTE(martin): send a dummy event to wake-up the run loop and exit from the run loop.
NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
location:NSMakePoint(0, 0)
modifierFlags:0
timestamp:0
windowNumber:0
context:nil
subtype:0
data1:0
data2:0];
[NSApp postEvent:event atStart:YES];
[NSApp stop:nil];
}
}
- (BOOL)application:(NSApplication*)application openFile:(NSString*)filename
{
oc_event event = { 0 };
event.window = (oc_window){ 0 };
event.type = OC_EVENT_PATHDROP;
oc_arena_scope scratch = oc_scratch_begin();
oc_str8 path = oc_str8_push_cstring(scratch.arena, [filename UTF8String]);
oc_str8_list_push(scratch.arena, &event.paths, path);
oc_queue_event(&event);
//NOTE: oc_queue_event copies paths to the event queue, so we can clear the arena scope here
oc_scratch_end(scratch);
return (YES);
}
- (void)handleAppleEvent:(NSAppleEventDescriptor*)appleEvent withReplyEvent:(NSAppleEventDescriptor*)replyEvent
{
NSString* nsPath = [[appleEvent paramDescriptorForKeyword:keyDirectObject] stringValue];
oc_event event = {};
event.window = (oc_window){ 0 };
event.type = OC_EVENT_PATHDROP;
oc_arena_scope scratch = oc_scratch_begin();
oc_str8 path = oc_str8_push_cstring(scratch.arena, [nsPath UTF8String]);
oc_str8_list_push(scratch.arena, &event.paths, path);
oc_queue_event(&event);
//NOTE: oc_queue_event copies paths to the event queue, so we can clear the arena scope here
oc_scratch_end(scratch);
}
//TODO: drag and drop paths
@end // @implementation OCAppDelegate
//---------------------------------------------------------------
// Custom NSWindow
//---------------------------------------------------------------
@implementation OCWindow
- (id)initWithWindowData:(oc_window_data*)window contentRect:(NSRect)rect styleMask:(uint32)style
{
mpWindow = window;
return ([self initWithContentRect:rect styleMask:style backing:NSBackingStoreBuffered defer:NO]);
}
- (BOOL)canBecomeKeyWindow
{
return (!(mpWindow->style & OC_WINDOW_STYLE_NO_FOCUS));
}
/*
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
if([sender draggingSourceOperationMask] & NSDragOperationGeneric)
{
return NSDragOperationGeneric;
}
return NSDragOperationNone;
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{@autoreleasepool
{
NSPasteboard *pasteboard = [sender draggingPasteboard];
NSArray *types = [NSArray arrayWithObject:NSFilenamesPboardType];
NSString *desiredType = [pasteboard availableTypeFromArray:types];
NSData *data;
NSArray *array;
NSPoint point;
if (desiredType == nil) {
return NO;
}
data = [pasteboard dataForType:desiredType];
if (data == nil) {
return NO;
}
SDL_assert([desiredType isEqualToString:NSFilenamesPboardType]);
array = [pasteboard propertyListForType:@"NSFilenamesPboardType"];
// Code addon to update the mouse location
point = [sender draggingLocation];
mouse = SDL_GetMouse();
x = (int)point.x;
y = (int)(sdlwindow->h - point.y);
if (x >= 0 && x < sdlwindow->w && y >= 0 && y < sdlwindow->h) {
SDL_SendMouseMotion(sdlwindow, mouse->mouseID, 0, x, y);
}
// Code addon to update the mouse location
for (NSString *path in array) {
NSURL *fileURL = [NSURL fileURLWithPath:path];
NSNumber *isAlias = nil;
[fileURL getResourceValue:&isAlias forKey:NSURLIsAliasFileKey error:nil];
// If the URL is an alias, resolve it.
if ([isAlias boolValue]) {
NSURLBookmarkResolutionOptions opts = NSURLBookmarkResolutionWithoutMounting | NSURLBookmarkResolutionWithoutUI;
NSData *bookmark = [NSURL bookmarkDataWithContentsOfURL:fileURL error:nil];
if (bookmark != nil) {
NSURL *resolvedURL = [NSURL URLByResolvingBookmarkData:bookmark
options:opts
relativeToURL:nil
bookmarkDataIsStale:nil
error:nil];
if (resolvedURL != nil) {
fileURL = resolvedURL;
}
}
}
if (!SDL_SendDropFile(sdlwindow, [[fileURL path] UTF8String])) {
return NO;
}
}
SDL_SendDropComplete(sdlwindow);
return YES;
}
- (BOOL)wantsPeriodicDraggingUpdates;
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem;
*/
@end //@implementation OCWindow
//---------------------------------------------------------------
// Custom NSWindow delegate
//---------------------------------------------------------------
@interface OCWindowDelegate : NSObject
{
oc_window_data* mpWindow;
}
- (id)initWithWindowData:(oc_window_data*)window;
@end
@implementation OCWindowDelegate
- (id)initWithWindowData:(oc_window_data*)window
{
self = [super init];
if(self != nil)
{
mpWindow = window;
}
return (self);
}
- (void)windowDidBecomeKey:(NSNotification*)notification
{
oc_event event = {};
event.window = oc_window_handle_from_ptr(mpWindow);
event.type = OC_EVENT_WINDOW_FOCUS;
mpWindow->hidden = false;
oc_queue_event(&event);
}
- (void)windowDidResignKey:(NSNotification*)notification
{
oc_event event = {};
event.window = oc_window_handle_from_ptr(mpWindow);
event.type = OC_EVENT_WINDOW_UNFOCUS;
oc_queue_event(&event);
}
- (void)windowDidMove:(NSNotification*)notification
{
const NSRect contentRect = [[mpWindow->osx.nsWindow contentView] frame];
const NSRect frameRect = [mpWindow->osx.nsWindow frame];
NSScreen* screen = mpWindow->osx.nsWindow.screen;
oc_event event = {};
event.window = oc_window_handle_from_ptr(mpWindow);
event.type = OC_EVENT_WINDOW_MOVE;
event.move.frame.x = frameRect.origin.x;
event.move.frame.y = screen.frame.size.height - frameRect.origin.y - frameRect.size.height;
event.move.frame.w = frameRect.size.width;
event.move.frame.h = frameRect.size.height;
event.move.content.x = frameRect.origin.x + contentRect.origin.x;
event.move.content.y = screen.frame.size.height - frameRect.origin.y - contentRect.origin.y - contentRect.size.height;
event.move.content.w = contentRect.size.width;
event.move.content.h = contentRect.size.height;
oc_queue_event(&event);
}
- (void)windowDidResize:(NSNotification*)notification
{
const NSRect contentRect = [[mpWindow->osx.nsWindow contentView] frame];
const NSRect frameRect = [mpWindow->osx.nsWindow frame];
NSScreen* screen = mpWindow->osx.nsWindow.screen;
oc_event event = {};
event.window = oc_window_handle_from_ptr(mpWindow);
event.type = OC_EVENT_WINDOW_RESIZE;
event.move.frame.x = frameRect.origin.x;
event.move.frame.y = screen.frame.size.height - frameRect.origin.y - frameRect.size.height;
event.move.frame.w = frameRect.size.width;
event.move.frame.h = frameRect.size.height;
event.move.content.x = frameRect.origin.x + contentRect.origin.x;
event.move.content.y = screen.frame.size.height - frameRect.origin.y - contentRect.origin.y - contentRect.size.height;
event.move.content.w = contentRect.size.width;
event.move.content.h = contentRect.size.height;
if(oc_appData.liveResizeCallback)
{
oc_appData.liveResizeCallback(event, oc_appData.liveResizeData);
}
//TODO: also ensure we don't overflow the queue during live resize...
oc_queue_event(&event);
}
- (void)windowWillStartLiveResize:(NSNotification*)notification
{
//TODO
}
- (void)windowDidEndLiveResize:(NSNotification*)notification
{
//TODO
}
- (void)windowWillClose:(NSNotification*)notification
{
mpWindow->osx.nsWindow = nil;
[mpWindow->osx.nsView release];
mpWindow->osx.nsView = nil;
[mpWindow->osx.nsWindowDelegate release];
mpWindow->osx.nsWindowDelegate = nil;
oc_window_recycle_ptr(mpWindow);
}
- (BOOL)windowShouldClose:(id)sender
{
mpWindow->shouldClose = true;
oc_event event = {};
event.window = oc_window_handle_from_ptr(mpWindow);
event.type = OC_EVENT_WINDOW_CLOSE;
oc_queue_event(&event);
return (mpWindow->shouldClose);
}
@end //@implementation OCWindowDelegate
//---------------------------------------------------------------
// Custom NSView
//---------------------------------------------------------------
@interface OCView : NSView <NSTextInputClient>
{
oc_window_data* window;
NSTrackingArea* trackingArea;
NSMutableAttributedString* markedText;
}
- (id)initWithWindowData:(oc_window_data*)mpWindow;
@end
@implementation OCView
- (id)initWithWindowData:(oc_window_data*)mpWindow
{
self = [super init];
if(self != nil)
{
window = mpWindow;
mpWindow->osx.nsView = self;
[mpWindow->osx.nsView setWantsLayer:YES];
mpWindow->osx.nsView.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
NSTrackingAreaOptions trackingOptions = NSTrackingMouseEnteredAndExited
| NSTrackingMouseMoved
| NSTrackingCursorUpdate
| NSTrackingActiveInActiveApp //TODO maybe change that to allow multi-window mouse events...
| NSTrackingEnabledDuringMouseDrag
| NSTrackingInVisibleRect
| NSTrackingAssumeInside;
trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:trackingOptions owner:self userInfo:nil];
[self addTrackingArea:trackingArea];
markedText = [[NSMutableAttributedString alloc] init];
}
return (self);
}
- (void)dealloc
{
[trackingArea release];
[markedText release];
[super dealloc];
}
- (void)drawRect:(NSRect)dirtyRect
{
if(window->style & OC_WINDOW_STYLE_NO_TITLE)
{
[NSGraphicsContext saveGraphicsState];
NSBezierPath* path = [NSBezierPath bezierPathWithRoundedRect:[self frame] xRadius:5 yRadius:5];
[path addClip];
[[NSColor whiteColor] set];
NSRectFill([self frame]);
}
if(window->style & OC_WINDOW_STYLE_NO_TITLE)
{
[NSGraphicsContext restoreGraphicsState];
[window->osx.nsWindow invalidateShadow];
}
}
- (BOOL)acceptsFirstReponder
{
return (YES);
}
- (void)cursorUpdate:(NSEvent*)event
{
if(oc_appData.osx.cursor)
{
[oc_appData.osx.cursor set];
}
else
{
[[NSCursor arrowCursor] set];
}
}
static void oc_process_mouse_button(NSEvent* nsEvent, oc_window_data* window, oc_mouse_button button, oc_key_action action)
{
oc_event event = {};
event.window = oc_window_handle_from_ptr(window);
event.type = OC_EVENT_MOUSE_BUTTON;
event.key.action = action;
event.key.code = button;
event.key.mods = oc_convert_osx_mods([nsEvent modifierFlags]);
event.key.clickCount = [nsEvent clickCount];
oc_queue_event(&event);
}
- (void)mouseDown:(NSEvent*)nsEvent
{
oc_process_mouse_button(nsEvent, window, OC_MOUSE_LEFT, OC_KEY_PRESS);
[window->osx.nsWindow makeFirstResponder:self];
}
- (void)mouseUp:(NSEvent*)nsEvent
{
oc_process_mouse_button(nsEvent, window, OC_MOUSE_LEFT, OC_KEY_RELEASE);
}
- (void)rightMouseDown:(NSEvent*)nsEvent
{
oc_process_mouse_button(nsEvent, window, OC_MOUSE_RIGHT, OC_KEY_PRESS);
}
- (void)rightMouseUp:(NSEvent*)nsEvent
{
oc_process_mouse_button(nsEvent, window, OC_MOUSE_RIGHT, OC_KEY_RELEASE);
}
- (void)otherMouseDown:(NSEvent*)nsEvent
{
oc_process_mouse_button(nsEvent, window, [nsEvent buttonNumber], OC_KEY_PRESS);
}
- (void)otherMouseUp:(NSEvent*)nsEvent
{
oc_process_mouse_button(nsEvent, window, [nsEvent buttonNumber], OC_KEY_RELEASE);
}
- (void)mouseDragged:(NSEvent*)nsEvent
{
[self mouseMoved:nsEvent];
}
- (void)mouseMoved:(NSEvent*)nsEvent
{
NSPoint p = [self convertPoint:[nsEvent locationInWindow] fromView:nil];
NSRect frame = [[window->osx.nsWindow contentView] frame];
oc_event event = {};
event.type = OC_EVENT_MOUSE_MOVE;
event.window = oc_window_handle_from_ptr(window);
event.mouse.x = p.x;
event.mouse.y = frame.size.height - p.y;
event.mouse.deltaX = [nsEvent deltaX];
event.mouse.deltaY = [nsEvent deltaY];
event.mouse.mods = oc_convert_osx_mods([nsEvent modifierFlags]);
oc_queue_event(&event);
}
- (void)scrollWheel:(NSEvent*)nsEvent
{
oc_event event = {};
event.window = oc_window_handle_from_ptr(window);
event.type = OC_EVENT_MOUSE_WHEEL;
double factor = [nsEvent hasPreciseScrollingDeltas] ? 0.1 : 1.0;
event.mouse.x = 0;
event.mouse.y = 0;
event.mouse.deltaX = -[nsEvent scrollingDeltaX] * factor;
event.mouse.deltaY = -[nsEvent scrollingDeltaY] * factor;
event.mouse.mods = oc_convert_osx_mods([nsEvent modifierFlags]);
oc_queue_event(&event);
}
- (void)mouseExited:(NSEvent*)nsEvent
{
oc_event event = {};
event.window = oc_window_handle_from_ptr(window);
event.type = OC_EVENT_MOUSE_LEAVE;
oc_queue_event(&event);
}
- (void)mouseEntered:(NSEvent*)nsEvent
{
oc_event event = {};
event.window = oc_window_handle_from_ptr(window);
event.type = OC_EVENT_MOUSE_ENTER;
oc_queue_event(&event);
}
- (void)keyDown:(NSEvent*)nsEvent
{
oc_key_action action = [nsEvent isARepeat] ? OC_KEY_REPEAT : OC_KEY_PRESS;
oc_event event = {};
event.window = oc_window_handle_from_ptr(window);
event.type = OC_EVENT_KEYBOARD_KEY;
event.key.action = action;
event.key.code = oc_convert_osx_key([nsEvent keyCode]);
event.key.mods = oc_convert_osx_mods([nsEvent modifierFlags]);
oc_str8 label = oc_key_to_label(event.key.code);
event.key.labelLen = label.len;
memcpy(event.key.label, label.ptr, label.len);
oc_queue_event(&event);
[self interpretKeyEvents:@[ nsEvent ]];
}
- (void)keyUp:(NSEvent*)nsEvent
{
oc_event event = {};
event.window = oc_window_handle_from_ptr(window);
event.type = OC_EVENT_KEYBOARD_KEY;
event.key.action = OC_KEY_RELEASE;
event.key.code = oc_convert_osx_key([nsEvent keyCode]);
event.key.mods = oc_convert_osx_mods([nsEvent modifierFlags]);
oc_queue_event(&event);
}
- (void)flagsChanged:(NSEvent*)nsEvent
{
oc_event event = {};
event.window = oc_window_handle_from_ptr(window);
event.type = OC_EVENT_KEYBOARD_MODS;
event.key.mods = oc_convert_osx_mods([nsEvent modifierFlags]);
oc_queue_event(&event);
}
- (BOOL)performKeyEquivalent:(NSEvent*)nsEvent
{
if([nsEvent modifierFlags] & NSEventModifierFlagCommand)
{
if([nsEvent charactersIgnoringModifiers] == [NSString stringWithUTF8String:"w"])
{
[window->osx.nsWindow performClose:self];
return (YES);
}
else if([nsEvent charactersIgnoringModifiers] == [NSString stringWithUTF8String:"q"])
{
oc_appData.shouldQuit = true;
oc_event event = {};
event.type = OC_EVENT_QUIT;
oc_queue_event(&event);
//[NSApp terminate:self];
return (YES);
}
}
return ([super performKeyEquivalent:nsEvent]);
}
- (BOOL)hasMarkedText
{
return ([markedText length] > 0);
}
static const NSRange kEmptyRange = { NSNotFound, 0 };
- (NSRange)markedRange
{
if([markedText length] > 0)
{
return (NSMakeRange(0, [markedText length] - 1));
}
else
{
return (kEmptyRange);
}
}
- (NSRange)selectedRange
{
return (kEmptyRange);
}
- (void)setMarkedText:(id)string
selectedRange:(NSRange)selectedRange
replacementRange:(NSRange)replacementRange
{
[markedText release];
if([string isKindOfClass:[NSAttributedString class]])
{
markedText = [[NSMutableAttributedString alloc] initWithAttributedString:string];
}
else
{
markedText = [[NSMutableAttributedString alloc] initWithString:string];
}
}
- (void)unmarkText
{
[[markedText mutableString] setString:@""];
}
- (NSArray*)validAttributesForMarkedText
{
return ([NSArray array]);
}
- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
actualRange:(NSRangePointer)actualRange
{
return (nil);
}
- (NSUInteger)characterIndexForPoint:(NSPoint)point
{
return (0);
}
- (NSRect)firstRectForCharacterRange:(NSRange)range
actualRange:(NSRangePointer)actualRange
{
NSRect frame = [window->osx.nsView frame];
return (NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0));
}
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
{
NSString* characters;
NSEvent* nsEvent = [NSApp currentEvent];
oc_keymod_flags mods = oc_convert_osx_mods([nsEvent modifierFlags]);
if([string isKindOfClass:[NSAttributedString class]])
{
characters = [string string];
}
else
{
characters = (NSString*)string;
}
NSRange range = NSMakeRange(0, [characters length]);
while(range.length)
{
oc_utf32 codepoint = 0;
if([characters getBytes:&codepoint
maxLength:sizeof(codepoint)
usedLength:NULL
encoding:NSUTF32StringEncoding
options:0
range:range
remainingRange:&range])
{
if(codepoint >= 0xf700 && codepoint <= 0xf7ff)
{
continue;
}
oc_event event = {};
event.window = oc_window_handle_from_ptr(window);
event.type = OC_EVENT_KEYBOARD_CHAR;
event.character.codepoint = codepoint;
oc_str8 seq = oc_utf8_encode(event.character.sequence, event.character.codepoint);
event.character.seqLen = seq.len;
oc_queue_event(&event);
}
}
[self unmarkText];
}
- (void)doCommandBySelector:(SEL)selector
{
}
@end //@implementation OCView
//***************************************************************
// public API
//***************************************************************
//---------------------------------------------------------------
// App public API
//---------------------------------------------------------------
void oc_init()
{
@autoreleasepool
{
if(!oc_appData.init)
{
memset(&oc_appData, 0, sizeof(oc_appData));
oc_arena_init(&oc_appData.eventArena);
oc_clock_init();
oc_init_osx_keys();
oc_update_keyboard_layout();
oc_install_keyboard_layout_listener();
oc_init_window_handles();
oc_ringbuffer_init(&oc_appData.eventQueue, 16);
[OCApplication sharedApplication];
OCAppDelegate* delegate = [[OCAppDelegate alloc] init];
[NSApp setDelegate:delegate];
[NSThread detachNewThreadSelector:@selector(noOpThread:)
toTarget:NSApp
withObject:nil];
oc_appData.init = true;
[NSApp run];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp activateIgnoringOtherApps:YES];
}
}
}
void oc_terminate()
{
//TODO: proper app data cleanup (eg delegate, etc)
if(oc_appData.init)
{
oc_arena_cleanup(&oc_appData.eventArena);
oc_appData = (oc_app){ 0 };
}
}
bool oc_should_quit()
{
return (oc_appData.shouldQuit);
}
void oc_do_quit()
{
oc_appData.shouldQuit = true;
}
void oc_cancel_quit()
{
oc_appData.shouldQuit = false;
}
void oc_request_quit()
{
oc_appData.shouldQuit = true;
oc_event event = {};
event.type = OC_EVENT_QUIT;
oc_queue_event(&event);
}
void oc_set_cursor(oc_mouse_cursor cursor)
{
switch(cursor)
{
case OC_MOUSE_CURSOR_ARROW:
{
oc_appData.osx.cursor = [NSCursor arrowCursor];
}
break;
case OC_MOUSE_CURSOR_RESIZE_0:
{
oc_appData.osx.cursor = [[NSCursor class] performSelector:@selector(_windowResizeEastWestCursor)];
}
break;
case OC_MOUSE_CURSOR_RESIZE_90:
{
oc_appData.osx.cursor = [[NSCursor class] performSelector:@selector(_windowResizeNorthSouthCursor)];
}
break;
case OC_MOUSE_CURSOR_RESIZE_45:
{
oc_appData.osx.cursor = [[NSCursor class] performSelector:@selector(_windowResizeNorthEastSouthWestCursor)];
}
break;
case OC_MOUSE_CURSOR_RESIZE_135:
{
oc_appData.osx.cursor = [[NSCursor class] performSelector:@selector(_windowResizeNorthWestSouthEastCursor)];
}
break;
case OC_MOUSE_CURSOR_TEXT:
{
oc_appData.osx.cursor = [NSCursor IBeamCursor];
}
break;
}
[oc_appData.osx.cursor set];
}
void oc_clipboard_clear()
{
@autoreleasepool
{
NSPasteboard* pb = [NSPasteboard generalPasteboard];
[pb clearContents];
}
}
void oc_clipboard_set_string(oc_str8 string)
{
@autoreleasepool
{
NSString* nsString = [[NSString alloc] initWithBytes:string.ptr length:string.len encoding:NSUTF8StringEncoding];
NSPasteboard* pb = [NSPasteboard generalPasteboard];
[pb writeObjects:[[NSArray alloc] initWithObjects:nsString, nil]];
}
}
oc_str8 oc_clipboard_copy_string(oc_str8 backing)
{
@autoreleasepool
{
//WARN(martin): maxSize includes space for a null terminator
NSPasteboard* pb = [NSPasteboard generalPasteboard];
NSString* nsString = [pb stringForType:NSPasteboardTypeString];
const char* cString = [nsString UTF8String];
u32 len = oc_min(backing.len - 1, strlen(cString)); //length without null terminator
strncpy(backing.ptr, cString, backing.len - 1);
backing.ptr[len] = '\0';
oc_str8 result = oc_str8_slice(backing, 0, len);
return (result);
}
}
oc_str8 oc_clipboard_get_string(oc_arena* arena)
{
@autoreleasepool
{
//WARN(martin): maxSize includes space for a null terminator
NSPasteboard* pb = [NSPasteboard generalPasteboard];
NSString* nsString = [pb stringForType:NSPasteboardTypeString];
const char* cString = [nsString UTF8String];
oc_str8 result = oc_str8_push_cstring(arena, cString);
return (result);
}
}
bool oc_clipboard_has_tag(const char* tag)
{
@autoreleasepool
{
NSString* tagString = [[NSString alloc] initWithUTF8String:tag];
NSArray* tagArray = [NSArray arrayWithObjects:tagString, nil];
NSPasteboard* pb = [NSPasteboard generalPasteboard];
NSString* available = [pb availableTypeFromArray:tagArray];
return (available != nil);
}
}
void oc_clipboard_set_data_for_tag(const char* tag, oc_str8 string)
{
@autoreleasepool
{
NSString* tagString = [[NSString alloc] initWithUTF8String:tag];
NSArray* tagArray = [NSArray arrayWithObjects:tagString, nil];
NSData* nsData = [NSData dataWithBytes:string.ptr length:string.len];
NSPasteboard* pb = [NSPasteboard generalPasteboard];
[pb addTypes:tagArray owner:nil];
[pb setData:nsData forType:tagString];
}
}
oc_str8 oc_clipboard_get_data_for_tag(oc_arena* arena, const char* tag)
{
@autoreleasepool
{
NSString* tagString = [[NSString alloc] initWithUTF8String:tag];
NSPasteboard* pb = [NSPasteboard generalPasteboard];
NSData* nsData = [pb dataForType:tagString];
oc_str8 result = oc_str8_push_buffer(arena, [nsData length], (char*)[nsData bytes]);
return (result);
}
}
//---------------------------------------------------------------
// Window public API
//---------------------------------------------------------------
oc_window oc_window_create(oc_rect contentRect, oc_str8 title, oc_window_style style)
{
@autoreleasepool
{
oc_window_data* window = oc_window_alloc();
if(!window)
{
oc_log_error("Could not allocate window data\n");
return ((oc_window){ 0 });
}
window->style = style;
window->shouldClose = false;
window->hidden = true;
window->osx.layers = (oc_list){ 0 };
u32 styleMask = oc_osx_get_window_style_mask(style);
NSRect screenRect = [[NSScreen mainScreen] frame];
NSRect rect = NSMakeRect(contentRect.x,
screenRect.size.height - contentRect.y - contentRect.h,
contentRect.w,
contentRect.h);
window->osx.nsWindow = [[OCWindow alloc] initWithWindowData:window contentRect:rect styleMask:styleMask];
window->osx.nsWindowDelegate = [[OCWindowDelegate alloc] initWithWindowData:window];
[window->osx.nsWindow setDelegate:(id)window->osx.nsWindowDelegate];
[window->osx.nsWindow setTitle:[[NSString alloc] initWithBytes:(void*)title.ptr length:title.len encoding:NSUTF8StringEncoding]];
if(style & OC_WINDOW_STYLE_NO_TITLE)
{
[window->osx.nsWindow setOpaque:NO];
[window->osx.nsWindow setBackgroundColor:[NSColor clearColor]];
[window->osx.nsWindow setHasShadow:YES];
}
if(style & OC_WINDOW_STYLE_FLOAT)
{
[window->osx.nsWindow setLevel:NSFloatingWindowLevel];
[window->osx.nsWindow setHidesOnDeactivate:YES];
}
if(style & OC_WINDOW_STYLE_NO_BUTTONS)
{
[[window->osx.nsWindow standardWindowButton:NSWindowCloseButton] setHidden:YES];
[[window->osx.nsWindow standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES];
[[window->osx.nsWindow standardWindowButton:NSWindowZoomButton] setHidden:YES];
}
OCView* view = [[OCView alloc] initWithWindowData:window];
[view setCanDrawConcurrently:YES];
[window->osx.nsWindow setContentView:view];
[window->osx.nsWindow makeFirstResponder:view];
[window->osx.nsWindow setAcceptsMouseMovedEvents:YES];
oc_window windowHandle = oc_window_handle_from_ptr(window);
return (windowHandle);
} //autoreleasepool
}
void oc_window_destroy(oc_window window)
{
@autoreleasepool
{
oc_window_data* windowData = oc_window_ptr_from_handle(window);
if(windowData)
{
[windowData->osx.nsWindow orderOut:nil];
[windowData->osx.nsWindow setDelegate:nil];
[windowData->osx.nsWindowDelegate release];
windowData->osx.nsWindowDelegate = nil;
[windowData->osx.nsView release];
windowData->osx.nsView = nil;
[windowData->osx.nsWindow close]; //also release the window
oc_window_recycle_ptr(windowData);
}
} // autoreleasepool
}
bool oc_window_should_close(oc_window window)
{
oc_window_data* windowData = oc_window_ptr_from_handle(window);
if(windowData)
{
return (windowData->shouldClose);
}
else
{
return (false);
}
}
void oc_window_cancel_close(oc_window window)
{
oc_window_data* windowData = oc_window_ptr_from_handle(window);
if(windowData)
{
windowData->shouldClose = false;
}
}
void oc_window_request_close(oc_window window)
{
oc_window_data* windowData = oc_window_ptr_from_handle(window);
if(windowData)
{
[windowData->osx.nsWindow close];
//NOTE(martin): this will call our window delegate willClose method
}
}
void* oc_window_native_pointer(oc_window window)
{
oc_window_data* windowData = oc_window_ptr_from_handle(window);
if(windowData)
{
return ((__bridge void*)windowData->osx.nsWindow);
}
else
{
return (0);
}
}
void oc_window_center(oc_window window)
{
@autoreleasepool
{
oc_window_data* windowData = oc_window_ptr_from_handle(window);
if(windowData)
{
[windowData->osx.nsWindow center];
}
}
}
bool oc_window_is_hidden(oc_window window)
{
oc_window_data* windowData = oc_window_ptr_from_handle(window);
if(windowData)
{
return (windowData->hidden);
}
else
{
return (false);
}
}
bool oc_window_is_focused(oc_window window)
{
@autoreleasepool
{
oc_window_data* windowData = oc_window_ptr_from_handle(window);
if(windowData)
{
return ([windowData->osx.nsWindow isKeyWindow]);
}
else
{
return (false);
}
}
}
void oc_window_set_title(oc_window window, oc_str8 title)
{
dispatch_block_t block = ^{
@autoreleasepool
{
oc_window_data* windowData = oc_window_ptr_from_handle(window);
if(windowData)
{
windowData->osx.nsWindow.title = [[NSString alloc] initWithBytes:title.ptr length:title.len encoding:NSUTF8StringEncoding];
}
}
};
if([NSThread isMainThread])
{
block();
}
else
{
dispatch_sync(dispatch_get_main_queue(), block);
}
}
bool oc_window_is_minimized(oc_window window)
{
@autoreleasepool
{
oc_window_data* windowData = oc_window_ptr_from_handle(window);
if(windowData)
{
return ([windowData->osx.nsWindow isMiniaturized]);
}
else
{
return (false);
}
}
}
void oc_window_hide(oc_window window)
{
@autoreleasepool
{
oc_window_data* windowData = oc_window_ptr_from_handle(window);
if(windowData)
{
windowData->hidden = true;
[windowData->osx.nsWindow orderOut:nil];
}
}
}
void oc_window_focus(oc_window window)
{
@autoreleasepool
{
oc_window_data* windowData = oc_window_ptr_from_handle(window);
if(windowData)
{
[windowData->osx.nsWindow makeKeyWindow];
}
}
}
void oc_window_minimize(oc_window window)
{
@autoreleasepool
{
oc_window_data* windowData = oc_window_ptr_from_handle(window);
if(windowData)
{
[windowData->osx.nsWindow miniaturize:windowData->osx.nsWindow];
}
}
}
void oc_window_restore(oc_window window)
{
@autoreleasepool
{
oc_window_data* windowData = oc_window_ptr_from_handle(window);
if(windowData && [windowData->osx.nsWindow isMiniaturized])
{
[windowData->osx.nsWindow deminiaturize:windowData->osx.nsWindow];
}
}
}
void oc_window_send_to_back(oc_window window)
{
@autoreleasepool
{
oc_window_data* windowData = oc_window_ptr_from_handle(window);
if(windowData)
{
[windowData->osx.nsWindow orderBack:nil];
}
}
}
void oc_window_bring_to_front(oc_window window)
{
@autoreleasepool
{
oc_window_data* windowData = oc_window_ptr_from_handle(window);
if(windowData)
{
windowData->hidden = false;
[windowData->osx.nsWindow orderFront:nil];
}
}
}
void oc_window_bring_to_front_and_focus(oc_window window)
{
oc_window_bring_to_front(window);
oc_window_focus(window);
}
oc_rect oc_window_get_frame_rect(oc_window window)
{
oc_rect rect = { 0 };
oc_window_data* windowData = oc_window_ptr_from_handle(window);
if(windowData)
{
NSRect frameRect = windowData->osx.nsWindow.frame;
NSScreen* screen = windowData->osx.nsWindow.screen;
rect = (oc_rect){
frameRect.origin.x,
screen.frame.size.height - frameRect.origin.y - frameRect.size.height,
frameRect.size.width,
frameRect.size.height
};
}
return (rect);
}
void oc_window_set_frame_rect(oc_window window, oc_rect rect)
{
@autoreleasepool
{
oc_window_data* windowData = oc_window_ptr_from_handle(window);
if(windowData)
{
NSScreen* screen = windowData->osx.nsWindow.screen;
NSRect frameRect = {
rect.x,
screen.frame.size.height - rect.y - rect.h,
rect.w,
rect.h
};
[windowData->osx.nsWindow setFrame:frameRect display:YES];
}
}
}
oc_rect oc_window_get_content_rect(oc_window window)
{
@autoreleasepool
{
oc_window_data* windowData = oc_window_ptr_from_handle(window);
if(windowData)
{
NSScreen* screen = [windowData->osx.nsWindow screen];
NSView* view = [windowData->osx.nsWindow contentView];
NSRect contentRect = [windowData->osx.nsWindow convertRectToScreen:view.frame];
oc_rect rect = {
contentRect.origin.x,
screen.frame.size.height - contentRect.origin.y - contentRect.size.height,
contentRect.size.width,
contentRect.size.height
};
return (rect);
}
else
{
return ((oc_rect){});
}
}
}
void oc_window_set_content_rect(oc_window window, oc_rect rect)
{
dispatch_block_t block = ^{
@autoreleasepool
{
oc_window_data* windowData = oc_window_ptr_from_handle(window);
if(windowData)
{
NSScreen* screen = [windowData->osx.nsWindow screen];
NSRect contentRect = {
rect.x,
screen.frame.size.height - rect.y - rect.h,
rect.w,
rect.h
};
NSRect frameRect = [windowData->osx.nsWindow frameRectForContentRect:contentRect];
[windowData->osx.nsWindow setFrame:frameRect display:YES];
}
}
};
if([NSThread isMainThread])
{
block();
}
else
{
dispatch_sync(dispatch_get_main_queue(), block);
}
}
//--------------------------------------------------------------------
// platform surface
//--------------------------------------------------------------------
void* oc_osx_surface_native_layer(oc_surface_data* surface)
{
return ((void*)surface->layer.caLayer);
}
oc_vec2 oc_osx_surface_contents_scaling(oc_surface_data* surface)
{
@autoreleasepool
{
f32 contentsScale = [surface->layer.caLayer contentsScale];
oc_vec2 res = { contentsScale, contentsScale };
return (res);
}
}
oc_vec2 oc_osx_surface_get_size(oc_surface_data* surface)
{
@autoreleasepool
{
CGRect bounds = surface->layer.caLayer.bounds;
oc_vec2 res = { bounds.size.width, bounds.size.height };
return (res);
}
}
bool oc_osx_surface_get_hidden(oc_surface_data* surface)
{
@autoreleasepool
{
return ([surface->layer.caLayer isHidden]);
}
}
void oc_osx_surface_set_hidden(oc_surface_data* surface, bool hidden)
{
@autoreleasepool
{
[CATransaction begin];
[CATransaction setDisableActions:YES];
[surface->layer.caLayer setHidden:hidden];
[CATransaction commit];
}
}
void oc_osx_update_layers(oc_window_data* window)
{
@autoreleasepool
{
int z = 0;
oc_list_for(&window->osx.layers, layer, oc_layer, listElt)
{
layer->caLayer.zPosition = (CGFloat)z;
z++;
}
}
}
void oc_osx_surface_bring_to_front(oc_surface_data* surface)
{
dispatch_block_t block = ^{
@autoreleasepool
{
oc_window_data* window = oc_window_ptr_from_handle(surface->layer.window);
if(window)
{
oc_list_remove(&window->osx.layers, &surface->layer.listElt);
oc_list_push_back(&window->osx.layers, &surface->layer.listElt);
oc_osx_update_layers(window);
}
}
};
if([NSThread isMainThread])
{
block();
}
else
{
dispatch_sync(dispatch_get_main_queue(), block);
}
}
void oc_osx_surface_send_to_back(oc_surface_data* surface)
{
dispatch_block_t block = ^{
@autoreleasepool
{
oc_window_data* window = oc_window_ptr_from_handle(surface->layer.window);
if(window)
{
oc_list_remove(&window->osx.layers, &surface->layer.listElt);
oc_list_push(&window->osx.layers, &surface->layer.listElt);
oc_osx_update_layers(window);
}
}
};
if([NSThread isMainThread])
{
block();
}
else
{
dispatch_sync(dispatch_get_main_queue(), block);
}
}
void oc_surface_cleanup(oc_surface_data* surface)
{
@autoreleasepool
{
oc_window_data* window = oc_window_ptr_from_handle(surface->layer.window);
if(window)
{
oc_list_remove(&window->osx.layers, &surface->layer.listElt);
oc_osx_update_layers(window);
}
[surface->layer.caLayer release];
}
}
void oc_surface_init_for_window(oc_surface_data* surface, oc_window_data* window)
{
@autoreleasepool
{
surface->layer.window = oc_window_handle_from_ptr(window);
surface->nativeLayer = oc_osx_surface_native_layer;
surface->contentsScaling = oc_osx_surface_contents_scaling;
surface->getSize = oc_osx_surface_get_size;
surface->getHidden = oc_osx_surface_get_hidden;
surface->setHidden = oc_osx_surface_set_hidden;
surface->bringToFront = oc_osx_surface_bring_to_front;
surface->sendToBack = oc_osx_surface_send_to_back;
surface->layer.caLayer = [[CALayer alloc] init];
[surface->layer.caLayer retain];
NSRect frame = [[window->osx.nsWindow contentView] frame];
CGSize size = frame.size;
surface->layer.caLayer.frame = (CGRect){ { 0, 0 }, size };
surface->layer.caLayer.contentsScale = window->osx.nsView.layer.contentsScale;
surface->layer.caLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
[window->osx.nsView.layer addSublayer:surface->layer.caLayer];
oc_list_push_back(&window->osx.layers, &surface->layer.listElt);
oc_osx_update_layers(window);
}
}
//------------------------------------------------------------------------------------------------
// Remote surfaces
//------------------------------------------------------------------------------------------------
oc_surface_id oc_osx_surface_remote_id(oc_surface_data* surface)
{
oc_surface_id remoteID = 0;
if(surface->layer.caContext)
{
@autoreleasepool
{
remoteID = (oc_surface_id)[surface->layer.caContext contextId];
}
}
return (remoteID);
}
void oc_surface_init_remote(oc_surface_data* surface, u32 width, u32 height)
{
@autoreleasepool
{
surface->nativeLayer = oc_osx_surface_native_layer;
surface->contentsScaling = oc_osx_surface_contents_scaling;
surface->getSize = oc_osx_surface_get_size;
surface->getHidden = oc_osx_surface_get_hidden;
surface->setHidden = oc_osx_surface_set_hidden;
surface->remoteID = oc_osx_surface_remote_id;
surface->layer.caLayer = [[CALayer alloc] init];
[surface->layer.caLayer retain];
[surface->layer.caLayer setFrame:(CGRect){ { 0, 0 }, { width, height } }];
NSDictionary* dict = [[NSDictionary alloc] init];
CGSConnectionID connectionID = CGSMainConnectionID();
surface->layer.caContext = [CAContext contextWithCGSConnection:connectionID options:dict];
[surface->layer.caContext retain];
[surface->layer.caContext setLayer:surface->layer.caLayer];
}
}
void oc_osx_surface_host_connect(oc_surface_data* surface, oc_surface_id remoteID)
{
@autoreleasepool
{
[(CALayerHost*)surface->layer.caLayer setContextId:(CAContextID)remoteID];
}
}
void oc_surface_init_host(oc_surface_data* surface, oc_window_data* window)
{
@autoreleasepool
{
surface->api = OC_HOST;
surface->nativeLayer = oc_osx_surface_native_layer;
surface->contentsScaling = oc_osx_surface_contents_scaling;
surface->getSize = oc_osx_surface_get_size;
surface->getHidden = oc_osx_surface_get_hidden;
surface->setHidden = oc_osx_surface_set_hidden;
surface->hostConnect = oc_osx_surface_host_connect;
surface->layer.caLayer = [[CALayerHost alloc] init];
[surface->layer.caLayer retain];
NSRect frame = [[window->osx.nsWindow contentView] frame];
CGSize size = frame.size;
[surface->layer.caLayer setFrame:(CGRect){ { 0, 0 }, size }];
[window->osx.nsView.layer addSublayer:surface->layer.caLayer];
}
}
oc_surface_data* oc_osx_surface_create_host(oc_window windowHandle)
{
oc_surface_data* surface = 0;
oc_window_data* window = oc_window_ptr_from_handle(windowHandle);
if(window)
{
surface = oc_malloc_type(oc_surface_data);
if(surface)
{
oc_surface_init_host(surface, window);
}
}
return (surface);
}
//--------------------------------------------------------------------
// view management
//--------------------------------------------------------------------
/*
oc_view oc_view_create(oc_window windowHandle, oc_rect frame)
{@autoreleasepool{
oc_window_data* window = oc_window_ptr_from_handle(windowHandle);
if(!window)
{
oc_log_error("Can't create view for nil window\n");
return(oc_view_nil());
}
oc_view_data* view = oc_view_alloc();
if(!view)
{
oc_log_error("Could not allocate view data\n");
return(oc_view_nil());
}
view->window = windowHandle;
NSRect nsFrame = {{frame.x, frame.y}, {frame.w, frame.h}};
view->nsView = [[NSView alloc] initWithFrame: nsFrame];
[view->nsView setWantsLayer:YES];
[[window->osx.nsWindow contentView] addSubview: view->nsView];
return(oc_view_handle_from_ptr(view));
}}
void oc_view_destroy(oc_view viewHandle)
{@autoreleasepool{
oc_view_data* view = oc_view_ptr_from_handle(viewHandle);
if(!view)
{
return;
}
oc_window_data* window = oc_window_ptr_from_handle(view->window);
if(!window)
{
return;
}
[view->nsView removeFromSuperview];
oc_view_recycle_ptr(view);
}}
void oc_view_set_frame(oc_view viewHandle, oc_rect frame)
{
oc_view_data* view = oc_view_ptr_from_handle(viewHandle);
if(!view)
{
return;
}
NSRect nsFrame = {{frame.x, frame.y}, {frame.w, frame.h}};
[view->nsView setFrame: nsFrame];
if(!oc_surface_is_nil(view->surface))
{
oc_surface_resize(view->surface, frame.w, frame.h);
}
}
*/
//--------------------------------------------------------------------
// Main loop throttle
//--------------------------------------------------------------------
void oc_set_target_fps(u32 fps)
{
oc_appData.frameStats.targetFramePeriod = 1. / (f64)fps;
oc_appData.frameStats.workTime = 0;
oc_appData.frameStats.remainingTime = 0;
if(oc_appData.osx.frameTimer)
{
[oc_appData.osx.frameTimer invalidate];
}
oc_appData.osx.frameTimer = [NSTimer timerWithTimeInterval:oc_appData.frameStats.targetFramePeriod
target:[NSApp delegate]
selector:@selector(timerElapsed:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:oc_appData.osx.frameTimer forMode:NSRunLoopCommonModes];
}
/*
void oc_begin_frame()
{
oc_appData.frameStats.start = oc_get_elapsed_seconds();
LOG_DEBUG("workTime = %.6f (%.6f fps), remaining = %.6f\n",
oc_appData.frameStats.workTime,
1/oc_appData.frameStats.workTime,
oc_appData.frameStats.remainingTime);
}
void oc_end_frame()
{
oc_appData.frameStats.workTime = oc_get_elapsed_seconds() - oc_appData.frameStats.start;
oc_appData.frameStats.remainingTime = oc_appData.frameStats.targetFramePeriod - oc_appData.frameStats.workTime;
while(oc_appData.frameStats.remainingTime > 100e-9)
{
if(oc_appData.frameStats.remainingTime > 10e-6)
{
oc_sleep_nano(oc_appData.frameStats.remainingTime*0.8*1e9);
}
oc_appData.frameStats.workTime = oc_get_elapsed_seconds() - oc_appData.frameStats.start;
oc_appData.frameStats.remainingTime = oc_appData.frameStats.targetFramePeriod - oc_appData.frameStats.workTime;
}
}
*/
//--------------------------------------------------------------------
// Events handling
//--------------------------------------------------------------------
void oc_set_live_resize_callback(oc_live_resize_callback callback, void* data)
{
oc_appData.liveResizeCallback = callback;
oc_appData.liveResizeData = data;
}
void oc_pump_events(f64 timeout)
{
@autoreleasepool
{
bool accumulate = false;
NSDate* date = 0;
if(timeout > 0)
{
date = [NSDate dateWithTimeIntervalSinceNow:(double)timeout];
}
else if(timeout == 0)
{
date = [NSDate distantPast];
accumulate = true;
}
else
{
date = [NSDate distantFuture];
}
for(;;)
{
NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
untilDate:date
inMode:NSDefaultRunLoopMode
dequeue:YES];
if(event != nil)
{
[NSApp sendEvent:event];
if(!accumulate)
{
break;
}
}
else
{
break;
}
}
}
}
i32 oc_dispatch_on_main_thread_sync(oc_window main_window, oc_dispatch_proc proc, void* user)
{
__block i32 result = 0;
dispatch_block_t block = ^{
result = proc(user);
};
if([NSThread isMainThread])
{
block();
}
else
{
dispatch_sync(dispatch_get_main_queue(), block);
}
return (result);
}
//--------------------------------------------------------------------
// system dialogs windows
//--------------------------------------------------------------------
oc_str8 oc_open_dialog(oc_arena* arena,
oc_str8 title,
oc_str8 defaultPath,
oc_str8_list filters,
bool directory)
{
@autoreleasepool
{
NSWindow* keyWindow = [NSApp keyWindow];
NSOpenPanel* dialog = [NSOpenPanel openPanel];
[dialog setLevel:CGShieldingWindowLevel()];
if(filters.eltCount)
{
NSMutableArray* fileTypesArray = [NSMutableArray array];
oc_list_for(&filters.list, elt, oc_str8_elt, listElt)
{
oc_str8 string = elt->string;
NSString* filter = [[NSString alloc] initWithBytes:string.ptr length:string.len encoding:NSUTF8StringEncoding];
[fileTypesArray addObject:filter];
}
[dialog setAllowedFileTypes:fileTypesArray];
}
// Enable options in the dialog.
if(directory)
{
[dialog setCanChooseDirectories:YES];
}
else
{
[dialog setCanChooseFiles:YES];
}
[dialog setAllowsMultipleSelection:FALSE];
NSString* nsPath = 0;
;
if(defaultPath.len)
{
nsPath = [[NSString alloc] initWithBytes:defaultPath.ptr length:defaultPath.len encoding:NSUTF8StringEncoding];
}
else
{
nsPath = [NSString stringWithUTF8String:"~"];
}
nsPath = [nsPath stringByExpandingTildeInPath];
[dialog setDirectoryURL:[NSURL fileURLWithPath:nsPath]];
// Display the dialog box. If the OK pressed,
// process the files.
if([dialog runModal] == NSModalResponseOK)
{
// Gets list of all files selected
NSArray* files = [dialog URLs];
//TODO: Loop through the files and process them.
const char* result = [[[files objectAtIndex:0] path] UTF8String];
oc_str8 path = oc_str8_push_cstring(arena, result);
[keyWindow makeKeyWindow];
return (path);
}
else
{
[keyWindow makeKeyWindow];
return ((oc_str8){ 0, 0 });
}
}
}
oc_str8 oc_save_dialog(oc_arena* arena,
oc_str8 title,
oc_str8 defaultPath,
oc_str8_list filters)
{
@autoreleasepool
{
NSWindow* keyWindow = [NSApp keyWindow];
NSSavePanel* dialog = [NSSavePanel savePanel];
[dialog setLevel:CGShieldingWindowLevel()];
if(filters.eltCount)
{
NSMutableArray* fileTypesArray = [NSMutableArray array];
oc_list_for(&filters.list, elt, oc_str8_elt, listElt)
{
oc_str8 string = elt->string;
NSString* filter = [[NSString alloc] initWithBytes:string.ptr length:string.len encoding:NSUTF8StringEncoding];
[fileTypesArray addObject:filter];
}
[dialog setAllowedFileTypes:fileTypesArray];
}
NSString* nsPath = 0;
;
if(defaultPath.len)
{
nsPath = [[NSString alloc] initWithBytes:defaultPath.ptr length:defaultPath.len encoding:NSUTF8StringEncoding];
}
else
{
nsPath = [NSString stringWithUTF8String:"~"];
}
nsPath = [nsPath stringByExpandingTildeInPath];
[dialog setDirectoryURL:[NSURL fileURLWithPath:nsPath]];
// Display the dialog box. If the OK pressed,
// process the files.
if([dialog runModal] == NSModalResponseOK)
{
// Gets list of all files selected
NSURL* files = [dialog URL];
// Loop through the files and process them.
const char* result = [[files path] UTF8String];
oc_str8 path = oc_str8_push_cstring(arena, result);
[keyWindow makeKeyWindow];
return (path);
}
else
{
[keyWindow makeKeyWindow];
return ((oc_str8){ 0, 0 });
}
}
}
int oc_alert_popup(oc_str8 title,
oc_str8 message,
oc_str8_list options)
{
__block int result = 0;
dispatch_block_t block = ^{
@autoreleasepool
{
NSWindow* keyWindow = [NSApp keyWindow];
NSAlert* alert = [[NSAlert alloc] init];
NSString* string;
oc_list_for_reverse((oc_list*)&options.list, elt, oc_str8_elt, listElt)
{
string = [[NSString alloc] initWithBytes:elt->string.ptr length:elt->string.len encoding:NSUTF8StringEncoding];
[alert addButtonWithTitle:string];
[string release];
}
string = [[NSString alloc] initWithBytes:title.ptr length:title.len encoding:NSUTF8StringEncoding];
[alert setMessageText:string];
[string release];
string = [[NSString alloc] initWithBytes:message.ptr length:message.len encoding:NSUTF8StringEncoding];
[alert setInformativeText:string];
[string release];
[alert setAlertStyle:NSAlertStyleWarning];
result = options.eltCount - ([alert runModal] - NSAlertFirstButtonReturn) - 1;
[keyWindow makeKeyWindow];
}
};
if([NSThread isMainThread])
{
block();
}
else
{
dispatch_sync(dispatch_get_main_queue(), block);
}
return (result);
}
//--------------------------------------------------------------------
// file system stuff... //TODO: move elsewhere
//--------------------------------------------------------------------
int oc_file_move(oc_str8 from, oc_str8 to)
{
@autoreleasepool
{
NSString* nsFrom = [[NSString alloc] initWithBytes:from.ptr length:from.len encoding:NSUTF8StringEncoding];
NSString* nsTo = [[NSString alloc] initWithBytes:to.ptr length:to.len encoding:NSUTF8StringEncoding];
NSError* err;
if([[NSFileManager defaultManager] moveItemAtPath:nsFrom toPath:nsTo error:&err] == YES)
{
return (0);
}
else
{
return (-1);
}
}
}
int oc_file_remove(oc_str8 path)
{
@autoreleasepool
{
NSString* nsPath = [[NSString alloc] initWithBytes:path.ptr length:path.len encoding:NSUTF8StringEncoding];
NSError* err;
if([[NSFileManager defaultManager] removeItemAtPath:nsPath error:&err] == YES)
{
return (0);
}
else
{
return (-1);
}
}
}
int oc_directory_create(oc_str8 path)
{
@autoreleasepool
{
NSString* nsPath = [[NSString alloc] initWithBytes:path.ptr length:path.len encoding:NSUTF8StringEncoding];
NSError* err;
if([[NSFileManager defaultManager] createDirectoryAtPath:nsPath
withIntermediateDirectories:YES
attributes:nil
error:&err]
== YES)
{
return (0);
}
else
{
return (-1);
}
}
}