//***************************************************************** // // $file: osx_app.m $ // $author: Martin Fouilleul $ // $date: 16/05/2020 $ // $revision: $ // $note: (C) 2020 by Martin Fouilleul - all rights reserved $ // //***************************************************************** #import //CATransaction #include // 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 - (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 )sender { if([sender draggingSourceOperationMask] & NSDragOperationGeneric) { return NSDragOperationGeneric; } return NSDragOperationNone; } - (BOOL)performDragOperation:(id )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 { 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); } } }