2450 lines
71 KiB
Objective-C
2450 lines
71 KiB
Objective-C
/*************************************************************************
|
|
*
|
|
* Orca
|
|
* Copyright 2023 Martin Fouilleul and the Orca project contributors
|
|
* See LICENSE.txt for licensing information
|
|
*
|
|
**************************************************************************/
|
|
|
|
#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 "platform/platform_path.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.scanCodes, OC_SCANCODE_UNKNOWN, 256 * sizeof(int));
|
|
|
|
oc_appData.scanCodes[0x1D] = OC_SCANCODE_0;
|
|
oc_appData.scanCodes[0x12] = OC_SCANCODE_1;
|
|
oc_appData.scanCodes[0x13] = OC_SCANCODE_2;
|
|
oc_appData.scanCodes[0x14] = OC_SCANCODE_3;
|
|
oc_appData.scanCodes[0x15] = OC_SCANCODE_4;
|
|
oc_appData.scanCodes[0x17] = OC_SCANCODE_5;
|
|
oc_appData.scanCodes[0x16] = OC_SCANCODE_6;
|
|
oc_appData.scanCodes[0x1A] = OC_SCANCODE_7;
|
|
oc_appData.scanCodes[0x1C] = OC_SCANCODE_8;
|
|
oc_appData.scanCodes[0x19] = OC_SCANCODE_9;
|
|
oc_appData.scanCodes[0x00] = OC_SCANCODE_A;
|
|
oc_appData.scanCodes[0x0B] = OC_SCANCODE_B;
|
|
oc_appData.scanCodes[0x08] = OC_SCANCODE_C;
|
|
oc_appData.scanCodes[0x02] = OC_SCANCODE_D;
|
|
oc_appData.scanCodes[0x0E] = OC_SCANCODE_E;
|
|
oc_appData.scanCodes[0x03] = OC_SCANCODE_F;
|
|
oc_appData.scanCodes[0x05] = OC_SCANCODE_G;
|
|
oc_appData.scanCodes[0x04] = OC_SCANCODE_H;
|
|
oc_appData.scanCodes[0x22] = OC_SCANCODE_I;
|
|
oc_appData.scanCodes[0x26] = OC_SCANCODE_J;
|
|
oc_appData.scanCodes[0x28] = OC_SCANCODE_K;
|
|
oc_appData.scanCodes[0x25] = OC_SCANCODE_L;
|
|
oc_appData.scanCodes[0x2E] = OC_SCANCODE_M;
|
|
oc_appData.scanCodes[0x2D] = OC_SCANCODE_N;
|
|
oc_appData.scanCodes[0x1F] = OC_SCANCODE_O;
|
|
oc_appData.scanCodes[0x23] = OC_SCANCODE_P;
|
|
oc_appData.scanCodes[0x0C] = OC_SCANCODE_Q;
|
|
oc_appData.scanCodes[0x0F] = OC_SCANCODE_R;
|
|
oc_appData.scanCodes[0x01] = OC_SCANCODE_S;
|
|
oc_appData.scanCodes[0x11] = OC_SCANCODE_T;
|
|
oc_appData.scanCodes[0x20] = OC_SCANCODE_U;
|
|
oc_appData.scanCodes[0x09] = OC_SCANCODE_V;
|
|
oc_appData.scanCodes[0x0D] = OC_SCANCODE_W;
|
|
oc_appData.scanCodes[0x07] = OC_SCANCODE_X;
|
|
oc_appData.scanCodes[0x10] = OC_SCANCODE_Y;
|
|
oc_appData.scanCodes[0x06] = OC_SCANCODE_Z;
|
|
|
|
oc_appData.scanCodes[0x27] = OC_SCANCODE_APOSTROPHE;
|
|
oc_appData.scanCodes[0x2A] = OC_SCANCODE_BACKSLASH;
|
|
oc_appData.scanCodes[0x2B] = OC_SCANCODE_COMMA;
|
|
oc_appData.scanCodes[0x18] = OC_SCANCODE_EQUAL;
|
|
oc_appData.scanCodes[0x32] = OC_SCANCODE_GRAVE_ACCENT;
|
|
oc_appData.scanCodes[0x21] = OC_SCANCODE_LEFT_BRACKET;
|
|
oc_appData.scanCodes[0x1B] = OC_SCANCODE_MINUS;
|
|
oc_appData.scanCodes[0x2F] = OC_SCANCODE_PERIOD;
|
|
oc_appData.scanCodes[0x1E] = OC_SCANCODE_RIGHT_BRACKET;
|
|
oc_appData.scanCodes[0x29] = OC_SCANCODE_SEMICOLON;
|
|
oc_appData.scanCodes[0x2C] = OC_SCANCODE_SLASH;
|
|
oc_appData.scanCodes[0x0A] = OC_SCANCODE_WORLD_1;
|
|
|
|
oc_appData.scanCodes[0x33] = OC_SCANCODE_BACKSPACE;
|
|
oc_appData.scanCodes[0x39] = OC_SCANCODE_CAPS_LOCK;
|
|
oc_appData.scanCodes[0x75] = OC_SCANCODE_DELETE;
|
|
oc_appData.scanCodes[0x7D] = OC_SCANCODE_DOWN;
|
|
oc_appData.scanCodes[0x77] = OC_SCANCODE_END;
|
|
oc_appData.scanCodes[0x24] = OC_SCANCODE_ENTER;
|
|
oc_appData.scanCodes[0x35] = OC_SCANCODE_ESCAPE;
|
|
oc_appData.scanCodes[0x7A] = OC_SCANCODE_F1;
|
|
oc_appData.scanCodes[0x78] = OC_SCANCODE_F2;
|
|
oc_appData.scanCodes[0x63] = OC_SCANCODE_F3;
|
|
oc_appData.scanCodes[0x76] = OC_SCANCODE_F4;
|
|
oc_appData.scanCodes[0x60] = OC_SCANCODE_F5;
|
|
oc_appData.scanCodes[0x61] = OC_SCANCODE_F6;
|
|
oc_appData.scanCodes[0x62] = OC_SCANCODE_F7;
|
|
oc_appData.scanCodes[0x64] = OC_SCANCODE_F8;
|
|
oc_appData.scanCodes[0x65] = OC_SCANCODE_F9;
|
|
oc_appData.scanCodes[0x6D] = OC_SCANCODE_F10;
|
|
oc_appData.scanCodes[0x67] = OC_SCANCODE_F11;
|
|
oc_appData.scanCodes[0x6F] = OC_SCANCODE_F12;
|
|
oc_appData.scanCodes[0x69] = OC_SCANCODE_F13;
|
|
oc_appData.scanCodes[0x6B] = OC_SCANCODE_F14;
|
|
oc_appData.scanCodes[0x71] = OC_SCANCODE_F15;
|
|
oc_appData.scanCodes[0x6A] = OC_SCANCODE_F16;
|
|
oc_appData.scanCodes[0x40] = OC_SCANCODE_F17;
|
|
oc_appData.scanCodes[0x4F] = OC_SCANCODE_F18;
|
|
oc_appData.scanCodes[0x50] = OC_SCANCODE_F19;
|
|
oc_appData.scanCodes[0x5A] = OC_SCANCODE_F20;
|
|
oc_appData.scanCodes[0x73] = OC_SCANCODE_HOME;
|
|
oc_appData.scanCodes[0x72] = OC_SCANCODE_INSERT;
|
|
oc_appData.scanCodes[0x7B] = OC_SCANCODE_LEFT;
|
|
oc_appData.scanCodes[0x3A] = OC_SCANCODE_LEFT_ALT;
|
|
oc_appData.scanCodes[0x3B] = OC_SCANCODE_LEFT_CONTROL;
|
|
oc_appData.scanCodes[0x38] = OC_SCANCODE_LEFT_SHIFT;
|
|
oc_appData.scanCodes[0x37] = OC_SCANCODE_LEFT_SUPER;
|
|
oc_appData.scanCodes[0x6E] = OC_SCANCODE_MENU;
|
|
oc_appData.scanCodes[0x47] = OC_SCANCODE_NUM_LOCK;
|
|
oc_appData.scanCodes[0x79] = OC_SCANCODE_PAGE_DOWN;
|
|
oc_appData.scanCodes[0x74] = OC_SCANCODE_PAGE_UP;
|
|
oc_appData.scanCodes[0x7C] = OC_SCANCODE_RIGHT;
|
|
oc_appData.scanCodes[0x3D] = OC_SCANCODE_RIGHT_ALT;
|
|
oc_appData.scanCodes[0x3E] = OC_SCANCODE_RIGHT_CONTROL;
|
|
oc_appData.scanCodes[0x3C] = OC_SCANCODE_RIGHT_SHIFT;
|
|
oc_appData.scanCodes[0x36] = OC_SCANCODE_RIGHT_SUPER;
|
|
oc_appData.scanCodes[0x31] = OC_SCANCODE_SPACE;
|
|
oc_appData.scanCodes[0x30] = OC_SCANCODE_TAB;
|
|
oc_appData.scanCodes[0x7E] = OC_SCANCODE_UP;
|
|
|
|
oc_appData.scanCodes[0x52] = OC_SCANCODE_KP_0;
|
|
oc_appData.scanCodes[0x53] = OC_SCANCODE_KP_1;
|
|
oc_appData.scanCodes[0x54] = OC_SCANCODE_KP_2;
|
|
oc_appData.scanCodes[0x55] = OC_SCANCODE_KP_3;
|
|
oc_appData.scanCodes[0x56] = OC_SCANCODE_KP_4;
|
|
oc_appData.scanCodes[0x57] = OC_SCANCODE_KP_5;
|
|
oc_appData.scanCodes[0x58] = OC_SCANCODE_KP_6;
|
|
oc_appData.scanCodes[0x59] = OC_SCANCODE_KP_7;
|
|
oc_appData.scanCodes[0x5B] = OC_SCANCODE_KP_8;
|
|
oc_appData.scanCodes[0x5C] = OC_SCANCODE_KP_9;
|
|
oc_appData.scanCodes[0x45] = OC_SCANCODE_KP_ADD;
|
|
oc_appData.scanCodes[0x41] = OC_SCANCODE_KP_DECIMAL;
|
|
oc_appData.scanCodes[0x4B] = OC_SCANCODE_KP_DIVIDE;
|
|
oc_appData.scanCodes[0x4C] = OC_SCANCODE_KP_ENTER;
|
|
oc_appData.scanCodes[0x51] = OC_SCANCODE_KP_EQUAL;
|
|
oc_appData.scanCodes[0x43] = OC_SCANCODE_KP_MULTIPLY;
|
|
oc_appData.scanCodes[0x4E] = OC_SCANCODE_KP_SUBTRACT;
|
|
}
|
|
|
|
static int oc_convert_osx_key(unsigned short nsCode)
|
|
{
|
|
if(nsCode >= OC_SCANCODE_COUNT)
|
|
{
|
|
return (OC_SCANCODE_UNKNOWN);
|
|
}
|
|
else
|
|
{
|
|
return (oc_appData.scanCodes[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);
|
|
}
|
|
|
|
/////////////////////// WIP ////////////////////////////////////
|
|
static void oc_update_keyboard_layout()
|
|
{
|
|
@autoreleasepool
|
|
{
|
|
TISInputSourceRef kbLayoutInputSource = TISCopyCurrentKeyboardLayoutInputSource();
|
|
if(!kbLayoutInputSource)
|
|
{
|
|
oc_log_error("Failed to load keyboard layout\n");
|
|
goto end;
|
|
}
|
|
|
|
CFDataRef kbLayoutUnicodeData = TISGetInputSourceProperty(kbLayoutInputSource,
|
|
kTISPropertyUnicodeKeyLayoutData);
|
|
if(!kbLayoutUnicodeData)
|
|
{
|
|
oc_log_error("Failed to load keyboard layout\n");
|
|
goto end;
|
|
}
|
|
|
|
UCKeyboardLayout* kbdLayout = (UCKeyboardLayout*)[(NSData*)kbLayoutUnicodeData bytes];
|
|
UInt32 kbdType = LMGetKbdType();
|
|
|
|
//NOTE: default US layout
|
|
memcpy(oc_appData.keyMap, oc_defaultKeyMap, sizeof(oc_key_code) * OC_SCANCODE_COUNT);
|
|
|
|
for(int osxCode = 0; osxCode < OC_SCANCODE_COUNT; osxCode++)
|
|
{
|
|
oc_key_code keyCode = OC_KEY_UNKNOWN;
|
|
oc_scan_code scanCode = oc_appData.scanCodes[osxCode];
|
|
|
|
if(scanCode == OC_SCANCODE_ENTER)
|
|
{
|
|
oc_log_info("scan code enter\n");
|
|
}
|
|
|
|
if(scanCode != OC_SCANCODE_UNKNOWN && scanCode < 256)
|
|
{
|
|
UInt32 deadKeyState = 0;
|
|
UniChar characters[8];
|
|
UniCharCount characterCount = 0;
|
|
|
|
OSStatus status = UCKeyTranslate(kbdLayout,
|
|
osxCode,
|
|
kUCKeyActionDown,
|
|
0,
|
|
kbdType,
|
|
kUCKeyTranslateNoDeadKeysBit,
|
|
&deadKeyState,
|
|
sizeof(characters) / sizeof(UniChar),
|
|
&characterCount,
|
|
characters);
|
|
|
|
if(status == noErr)
|
|
{
|
|
oc_appData.keyMap[scanCode] = characters[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
//NOTE fix digit row for azerty keyboards
|
|
bool azerty = true;
|
|
for(int scanCode = OC_SCANCODE_0; scanCode <= OC_SCANCODE_9; scanCode++)
|
|
{
|
|
if(oc_appData.keyMap[scanCode] >= OC_KEY_0 && oc_appData.keyMap[scanCode] <= OC_KEY_9)
|
|
{
|
|
azerty = false;
|
|
break;
|
|
}
|
|
}
|
|
if(azerty)
|
|
{
|
|
for(int scanCode = OC_SCANCODE_0; scanCode <= OC_SCANCODE_9; scanCode++)
|
|
{
|
|
oc_appData.keyMap[scanCode] = OC_KEY_0 + (scanCode - OC_SCANCODE_0);
|
|
}
|
|
}
|
|
|
|
end:
|
|
kbLayoutUnicodeData = nil;
|
|
CFRelease(kbLayoutInputSource);
|
|
}
|
|
}
|
|
|
|
/*
|
|
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;
|
|
@public
|
|
CVDisplayLinkRef displayLink;
|
|
}
|
|
|
|
- (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 execute cleanup code. Use can then call oc_request_quit() to exit
|
|
// the main runloop
|
|
|
|
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;
|
|
|
|
//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
|
|
{
|
|
OCWindow* ocWindow = (OCWindow*)mpWindow->osx.nsWindow;
|
|
CVDisplayLinkStop(ocWindow->displayLink);
|
|
|
|
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.button = 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.scanCode = oc_convert_osx_key([nsEvent keyCode]);
|
|
event.key.keyCode = oc_scancode_to_keycode(event.key.scanCode);
|
|
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.scanCode = oc_convert_osx_key([nsEvent keyCode]);
|
|
event.key.keyCode = oc_scancode_to_keycode(event.key.scanCode);
|
|
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_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_cancel_quit()
|
|
{
|
|
oc_appData.shouldQuit = false;
|
|
}
|
|
|
|
void oc_request_quit()
|
|
{
|
|
oc_appData.shouldQuit = true;
|
|
|
|
@autoreleasepool
|
|
{
|
|
NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
|
|
location:NSMakePoint(0, 0)
|
|
modifierFlags:0
|
|
timestamp:0.0
|
|
windowNumber:0
|
|
context:nil
|
|
subtype:0
|
|
data1:0
|
|
data2:0];
|
|
|
|
[NSApp postEvent:event atStart:NO];
|
|
}
|
|
}
|
|
|
|
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 clearContents];
|
|
[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);
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
// Events handling
|
|
//--------------------------------------------------------------------
|
|
|
|
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)
|
|
{
|
|
__block oc_str8 path = { 0 };
|
|
|
|
dispatch_block_t block = ^{
|
|
@autoreleasepool
|
|
{
|
|
NSWindow* keyWindow = [NSApp keyWindow];
|
|
|
|
NSOpenPanel* dialog = [NSOpenPanel openPanel];
|
|
|
|
NSString* nsTitle = [[NSString alloc] initWithBytes:title.ptr length:title.len encoding:NSUTF8StringEncoding];
|
|
//NOTE: title is not displayed since OS X 10.11, now use setMessage instead.
|
|
// see https://stackoverflow.com/questions/36879212/title-bar-missing-in-nsopenpanel
|
|
[dialog setMessage:nsTitle];
|
|
|
|
[dialog setLevel:NSModalPanelWindowLevel];
|
|
|
|
if(filters.eltCount)
|
|
{
|
|
NSMutableArray* fileTypesArray = [NSMutableArray array];
|
|
|
|
oc_list_for((oc_list*)&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];
|
|
|
|
path = oc_str8_push_cstring(arena, result);
|
|
}
|
|
[keyWindow makeKeyWindow];
|
|
}
|
|
};
|
|
|
|
if([NSThread isMainThread])
|
|
{
|
|
block();
|
|
}
|
|
else
|
|
{
|
|
dispatch_sync(dispatch_get_main_queue(), block);
|
|
}
|
|
|
|
return (path);
|
|
}
|
|
|
|
oc_str8 oc_save_dialog(oc_arena* arena,
|
|
oc_str8 title,
|
|
oc_str8 defaultPath,
|
|
oc_str8_list filters)
|
|
{
|
|
__block oc_str8 path = { 0 };
|
|
|
|
dispatch_block_t block = ^{
|
|
@autoreleasepool
|
|
{
|
|
NSWindow* keyWindow = [NSApp keyWindow];
|
|
|
|
NSSavePanel* dialog = [NSSavePanel savePanel];
|
|
[dialog setLevel:CGShieldingWindowLevel()];
|
|
|
|
if(filters.eltCount)
|
|
{
|
|
NSMutableArray* fileTypesArray = [NSMutableArray array];
|
|
|
|
oc_list_for((oc_list*)&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];
|
|
|
|
path = oc_str8_push_cstring(arena, result);
|
|
}
|
|
[keyWindow makeKeyWindow];
|
|
}
|
|
};
|
|
|
|
if([NSThread isMainThread])
|
|
{
|
|
block();
|
|
}
|
|
else
|
|
{
|
|
dispatch_sync(dispatch_get_main_queue(), block);
|
|
}
|
|
|
|
return (path);
|
|
}
|
|
|
|
ORCA_API oc_file_dialog_result oc_file_dialog_for_table(oc_arena* arena, oc_file_dialog_desc* desc, oc_file_table* table)
|
|
{
|
|
__block oc_file_dialog_result result = { 0 };
|
|
|
|
dispatch_block_t block = ^{
|
|
@autoreleasepool
|
|
{
|
|
oc_arena_scope scratch = oc_scratch_begin_next(arena);
|
|
|
|
NSWindow* keyWindow = [NSApp keyWindow];
|
|
|
|
NSSavePanel* dialog = 0;
|
|
if(desc->kind == OC_FILE_DIALOG_OPEN)
|
|
{
|
|
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
|
|
dialog = (NSSavePanel*)openPanel;
|
|
|
|
openPanel.canChooseFiles = (desc->flags & OC_FILE_DIALOG_FILES) ? YES : NO;
|
|
openPanel.canChooseDirectories = (desc->flags & OC_FILE_DIALOG_DIRECTORIES) ? YES : NO;
|
|
openPanel.allowsMultipleSelection = (desc->flags & OC_FILE_DIALOG_MULTIPLE) ? YES : NO;
|
|
}
|
|
else
|
|
{
|
|
dialog = [NSSavePanel savePanel];
|
|
|
|
dialog.canCreateDirectories = (desc->flags & OC_FILE_DIALOG_CREATE_DIRECTORIES) ? YES : NO;
|
|
}
|
|
|
|
//NOTE: set title. "title" property is not displayed since OS X 10.11, now use setMessage instead.
|
|
// see https://stackoverflow.com/questions/36879212/title-bar-missing-in-nsopenpanel
|
|
NSString* nsTitle = [[NSString alloc] initWithBytes:desc->title.ptr
|
|
length:desc->title.len
|
|
encoding:NSUTF8StringEncoding];
|
|
[dialog setMessage:nsTitle];
|
|
|
|
//NOTE: set ok button
|
|
if(desc->okLabel.len)
|
|
{
|
|
NSString* label = [[NSString alloc] initWithBytes:desc->okLabel.ptr
|
|
length:desc->okLabel.len
|
|
encoding:NSUTF8StringEncoding];
|
|
|
|
[dialog setPrompt:label];
|
|
}
|
|
|
|
//NOTE: set starting path
|
|
oc_str8 startPath = { 0 };
|
|
{
|
|
oc_str8_list list = { 0 };
|
|
if(!oc_file_is_nil(desc->startAt))
|
|
{
|
|
oc_file_slot* slot = oc_file_slot_from_handle(table, desc->startAt);
|
|
if(slot)
|
|
{
|
|
char path[PATH_MAX];
|
|
if(fcntl(slot->fd, F_GETPATH, path) != -1)
|
|
{
|
|
oc_str8 string = oc_str8_push_cstring(scratch.arena, path);
|
|
oc_str8_list_push(scratch.arena, &list, string);
|
|
}
|
|
}
|
|
}
|
|
if(desc->startPath.len)
|
|
{
|
|
oc_str8_list_push(scratch.arena, &list, desc->startPath);
|
|
}
|
|
startPath = oc_path_join(scratch.arena, list);
|
|
}
|
|
|
|
NSString* nsPath = 0;
|
|
if(startPath.len)
|
|
{
|
|
nsPath = [[NSString alloc] initWithBytes:startPath.ptr
|
|
length:startPath.len
|
|
encoding:NSUTF8StringEncoding];
|
|
}
|
|
else
|
|
{
|
|
nsPath = [NSString stringWithUTF8String:"~"];
|
|
}
|
|
nsPath = [nsPath stringByExpandingTildeInPath];
|
|
[dialog setDirectoryURL:[NSURL fileURLWithPath:nsPath]];
|
|
|
|
//NOTE: set filters
|
|
if(desc->filters.eltCount)
|
|
{
|
|
NSMutableArray* fileTypesArray = [NSMutableArray array];
|
|
|
|
oc_list_for((oc_list*)&desc->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];
|
|
}
|
|
|
|
// Display the dialog box. If the OK pressed,
|
|
// process the files.
|
|
|
|
[dialog validateVisibleColumns];
|
|
[dialog setLevel:NSModalPanelWindowLevel];
|
|
|
|
if([dialog runModal] == NSModalResponseOK)
|
|
{
|
|
if(desc->kind == OC_FILE_DIALOG_OPEN && (desc->flags & OC_FILE_DIALOG_MULTIPLE))
|
|
{
|
|
// Gets list of all files selected
|
|
NSArray* files = [((NSOpenPanel*)dialog) URLs];
|
|
|
|
const char* path = [[[files objectAtIndex:0] path] UTF8String];
|
|
result.path = oc_str8_push_cstring(arena, path);
|
|
|
|
for(int i = 0; i < [files count]; i++)
|
|
{
|
|
const char* path = [[[files objectAtIndex:i] path] UTF8String];
|
|
oc_str8 string = oc_str8_push_cstring(arena, path);
|
|
oc_str8_list_push(arena, &result.selection, string);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const char* path = [[[dialog URL] path] UTF8String];
|
|
result.path = oc_str8_push_cstring(arena, path);
|
|
|
|
oc_str8_list_push(arena, &result.selection, result.path);
|
|
}
|
|
result.button = OC_FILE_DIALOG_OK;
|
|
}
|
|
else
|
|
{
|
|
result.button = OC_FILE_DIALOG_CANCEL;
|
|
}
|
|
[keyWindow makeKeyWindow];
|
|
|
|
oc_scratch_end(scratch);
|
|
}
|
|
};
|
|
|
|
if([NSThread isMainThread])
|
|
{
|
|
block();
|
|
}
|
|
else
|
|
{
|
|
dispatch_sync(dispatch_get_main_queue(), block);
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
static CVReturn oc_display_link_callback(
|
|
CVDisplayLinkRef displayLink,
|
|
const CVTimeStamp* inNow,
|
|
const CVTimeStamp* inOutputTime,
|
|
CVOptionFlags flagsIn,
|
|
CVOptionFlags* flagsOut,
|
|
void* displayLinkContext)
|
|
{
|
|
oc_event event = { 0 };
|
|
event.type = OC_EVENT_FRAME;
|
|
oc_queue_event(&event);
|
|
|
|
OCWindow* ocWindow = (OCWindow*)displayLinkContext;
|
|
|
|
CGDirectDisplayID displays[4];
|
|
uint32_t matchingDisplayCount;
|
|
CGError err = CGGetDisplaysWithRect(ocWindow.frame, 4, displays, &matchingDisplayCount);
|
|
if(err == kCGErrorSuccess)
|
|
{
|
|
// determine which display has the greatest intersecting area
|
|
if(matchingDisplayCount > 0)
|
|
{
|
|
CGDirectDisplayID* selectedDisplay = NULL;
|
|
CGFloat selectedIntersectArea = 0.0f;
|
|
|
|
for(uint32_t i = 0; i < matchingDisplayCount; ++i)
|
|
{
|
|
CGRect displayBounds = CGDisplayBounds(displays[i]);
|
|
CGRect intersection = CGRectIntersection(ocWindow.frame, displayBounds);
|
|
CGFloat intersectArea = intersection.size.width * intersection.size.width;
|
|
if(selectedDisplay == NULL || intersectArea < selectedIntersectArea)
|
|
{
|
|
selectedDisplay = displays + i;
|
|
selectedIntersectArea = intersectArea;
|
|
}
|
|
}
|
|
|
|
if(selectedDisplay)
|
|
{
|
|
CGDirectDisplayID currentDisplay = CVDisplayLinkGetCurrentCGDisplay(ocWindow->displayLink);
|
|
if(currentDisplay != *selectedDisplay)
|
|
{
|
|
CVDisplayLinkSetCurrentCGDisplay(ocWindow->displayLink, *selectedDisplay);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
oc_log_error("CGGetDisplaysWithRect failed with error: %d\n", err);
|
|
}
|
|
|
|
return kCVReturnSuccess;
|
|
}
|
|
|
|
void oc_vsync_init(void)
|
|
{
|
|
}
|
|
|
|
void oc_vsync_wait(oc_window window)
|
|
{
|
|
// TODO figure out why this causes stuttering with triple buffering
|
|
oc_window_data* windowData = oc_window_ptr_from_handle(window);
|
|
if(!windowData)
|
|
{
|
|
return;
|
|
}
|
|
|
|
OCWindow* ocWindow = (OCWindow*)windowData->osx.nsWindow;
|
|
|
|
CVReturn ret;
|
|
|
|
if((ret = CVDisplayLinkCreateWithActiveCGDisplays(&ocWindow->displayLink)) != kCVReturnSuccess)
|
|
{
|
|
oc_log_error("CVDisplayLinkCreateWithActiveCGDisplays error: %d\n", ret);
|
|
}
|
|
|
|
CGDirectDisplayID mainDisplay = CGMainDisplayID();
|
|
|
|
if((ret = CVDisplayLinkSetCurrentCGDisplay(ocWindow->displayLink, mainDisplay)) != kCVReturnSuccess)
|
|
{
|
|
oc_log_error("CVDisplayLinkSetCurrentCGDisplay ret: %d\n", ret);
|
|
}
|
|
|
|
if((ret = CVDisplayLinkSetOutputCallback(ocWindow->displayLink, oc_display_link_callback, ocWindow)) != kCVReturnSuccess)
|
|
{
|
|
oc_log_error("CVDisplayLinkSetOutputCallback ret: %d\n", ret);
|
|
}
|
|
|
|
if((ret = CVDisplayLinkStart(ocWindow->displayLink)) != kCVReturnSuccess)
|
|
{
|
|
oc_log_error("CVDisplayLinkStart ret: %d\n", ret);
|
|
}
|
|
}
|