From a58b0660a7162f6822b064dff1dec56790733802 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sat, 23 Jan 2021 10:46:28 +1100 Subject: [PATCH] Port most of cocoa window back to ObjC --- src/Window.c | 511 ++------------------------------------------ src/Window.h | 2 + src/interop_cocoa.m | 462 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 472 insertions(+), 503 deletions(-) diff --git a/src/Window.c b/src/Window.c index 0e3059029..bc354e2c7 100644 --- a/src/Window.c +++ b/src/Window.c @@ -15,8 +15,6 @@ struct _WinData WindowInfo; int Display_ScaleX(int x) { return (int)(x * DisplayInfo.ScaleX); } int Display_ScaleY(int y) { return (int)(y * DisplayInfo.ScaleY); } -#define Display_CentreX(width) (DisplayInfo.X + (DisplayInfo.Width - width) / 2) -#define Display_CentreY(height) (DisplayInfo.Y + (DisplayInfo.Height - height) / 2) #ifndef CC_BUILD_WEB void Clipboard_RequestText(RequestClipboardCallback callback, void* obj) { @@ -28,20 +26,29 @@ void Clipboard_RequestText(RequestClipboardCallback callback, void* obj) { } #endif -#ifdef CC_BUILD_IOS -/* iOS implements some functions in external interop_ios.m file */ -#define CC_MAYBE_OBJC extern +#if defined CC_BUILD_IOS +/* iOS implements these functions in external interop_ios.m file */ +#define CC_MAYBE_OBJC1 extern +#define CC_MAYBE_OBJC2 extern +#define CC_OBJC_VISIBLE +#elif defined CC_BUILD_COCOA +/* Cocoa implements some functions in external interop_cocoa.m file */ +#define CC_MAYBE_OBJC1 extern +#define CC_MAYBE_OBJC2 static +#define CC_OBJC_VISIBLE #else /* All other platforms implement internally in this file */ -#define CC_MAYBE_OBJC static +#define CC_MAYBE_OBJC1 static +#define CC_MAYBE_OBJC2 static +#define CC_OBJC_VISIBLE static #endif static int cursorPrevX, cursorPrevY; static cc_bool cursorVisible = true; /* Gets the position of the cursor in screen or window coordinates. */ -CC_MAYBE_OBJC void Cursor_GetRawPos(int* x, int* y); -CC_MAYBE_OBJC void Cursor_DoSetVisible(cc_bool visible); +CC_MAYBE_OBJC1 void Cursor_GetRawPos(int* x, int* y); +CC_MAYBE_OBJC2 void Cursor_DoSetVisible(cc_bool visible); void Cursor_SetVisible(cc_bool visible) { if (cursorVisible == visible) return; @@ -80,7 +87,7 @@ static void DefaultDisableRawMouse(void) { } /* The actual windowing system specific method to display a message box */ -CC_MAYBE_OBJC void ShowDialogCore(const char* title, const char* msg); +CC_MAYBE_OBJC1 void ShowDialogCore(const char* title, const char* msg); void Window_ShowDialog(const char* title, const char* msg) { /* Ensure cursor is visible while showing message box */ cc_bool visible = cursorVisible; @@ -2087,9 +2094,9 @@ void Window_DisableRawMouse(void) { *#########################################################################################################################*/ #elif defined CC_BUILD_CARBON || defined CC_BUILD_COCOA #include -static int windowX, windowY; +CC_OBJC_VISIBLE int windowX, windowY; -static void Window_CommonInit(void) { +CC_OBJC_VISIBLE void Window_CommonInit(void) { CGDirectDisplayID display = CGMainDisplayID(); CGRect bounds = CGDisplayBounds(display); @@ -2107,7 +2114,7 @@ static pascal OSErr HandleQuitMessage(const AppleEvent* ev, AppleEvent* reply, l return 0; } -static void Window_CommonCreate(void) { +CC_OBJC_VISIBLE void Window_CommonCreate(void) { WindowInfo.Exists = true; /* for quit buttons in dock and menubar */ AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, @@ -2125,7 +2132,7 @@ static const cc_uint8 key_map[8 * 16] = { KEY_F5, KEY_F6, KEY_F7, KEY_F3, KEY_F8, KEY_F9, 0, KEY_F11, 0, KEY_F13, 0, KEY_F14, 0, KEY_F10, 0, KEY_F12, 'U', KEY_F15, KEY_INSERT, KEY_HOME, KEY_PAGEUP, KEY_DELETE, KEY_F4, KEY_END, KEY_F2, KEY_PAGEDOWN, KEY_F1, KEY_LEFT, KEY_RIGHT, KEY_DOWN, KEY_UP, 0, }; -static int MapNativeKey(UInt32 key) { return key < Array_Elems(key_map) ? key_map[key] : 0; } +CC_OBJC_VISIBLE int MapNativeKey(UInt32 key) { return key < Array_Elems(key_map) ? key_map[key] : 0; } /* TODO: Check these.. */ /* case 0x37: return KEY_LWIN; */ /* case 0x38: return KEY_LSHIFT; */ @@ -2735,483 +2742,7 @@ void Window_FreeFramebuffer(struct Bitmap* bmp) { *-------------------------------------------------------Cocoa window------------------------------------------------------* *#########################################################################################################################*/ #elif defined CC_BUILD_COCOA -#include -#include -static id appHandle, winHandle, viewHandle; -extern void* NSDefaultRunLoopMode; - -static SEL selFrame, selDeltaX, selDeltaY; -static SEL selNextEvent, selType, selSendEvent; -static SEL selButton, selKeycode, selModifiers; -static SEL selCharacters, selUtf8String, selMouseLoc; -static SEL selCurrentContext, selGraphicsPort; -static SEL selSetNeedsDisplay, selDisplayIfNeeded; -static SEL selUpdate, selFlushBuffer; - -static void RegisterSelectors(void) { - selFrame = sel_registerName("frame"); - selDeltaX = sel_registerName("deltaX"); - selDeltaY = sel_registerName("deltaY"); - - selNextEvent = sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"); - selType = sel_registerName("type"); - selSendEvent = sel_registerName("sendEvent:"); - - selButton = sel_registerName("buttonNumber"); - selKeycode = sel_registerName("keyCode"); - selModifiers = sel_registerName("modifierFlags"); - - selCharacters = sel_registerName("characters"); - selUtf8String = sel_registerName("UTF8String"); - selMouseLoc = sel_registerName("mouseLocation"); - - selCurrentContext = sel_registerName("currentContext"); - selGraphicsPort = sel_registerName("graphicsPort"); - selSetNeedsDisplay = sel_registerName("setNeedsDisplayInRect:"); - selDisplayIfNeeded = sel_registerName("displayIfNeeded"); - - selUpdate = sel_registerName("update"); - selFlushBuffer = sel_registerName("flushBuffer"); -} - -static CC_INLINE CGFloat Send_CGFloat(id receiver, SEL sel) { - /* Sometimes we have to use fpret and sometimes we don't. See this for more details: */ - /* http://www.sealiesoftware.com/blog/archive/2008/11/16/objc_explain_objc_msgSend_fpret.html */ - /* return type is void*, but we cannot cast a void* to a float or double */ - -#ifdef __i386__ - return ((CGFloat(*)(id, SEL))(void *)objc_msgSend_fpret)(receiver, sel); -#else - return ((CGFloat(*)(id, SEL))(void *)objc_msgSend)(receiver, sel); -#endif -} - -static CC_INLINE CGPoint Send_CGPoint(id receiver, SEL sel) { - /* on x86 and x86_64 CGPoint fits the requirements for 'struct returned in registers' */ - return ((CGPoint(*)(id, SEL))(void *)objc_msgSend)(receiver, sel); -} - -static void RefreshWindowBounds(void) { - CGRect win, view; - int viewY; - - win = ((CGRect(*)(id, SEL))(void *)objc_msgSend_stret)(winHandle, selFrame); - view = ((CGRect(*)(id, SEL))(void *)objc_msgSend_stret)(viewHandle, selFrame); - - /* For cocoa, the 0,0 origin is the bottom left corner of windows/views/screen. */ - /* To get window's real Y screen position, first need to find Y of top. (win.y + win.height) */ - /* Then just subtract from screen height to make relative to top instead of bottom of the screen. */ - /* Of course this is only half the story, since we're really after Y position of the content. */ - /* To work out top Y of view relative to window, it's just win.height - (view.y + view.height) */ - viewY = (int)win.size.height - ((int)view.origin.y + (int)view.size.height); - windowX = (int)win.origin.x + (int)view.origin.x; - windowY = DisplayInfo.Height - ((int)win.origin.y + (int)win.size.height) + viewY; - - WindowInfo.Width = (int)view.size.width; - WindowInfo.Height = (int)view.size.height; -} - -static void OnDidResize(id self, SEL cmd, id notification) { - RefreshWindowBounds(); - Event_RaiseVoid(&WindowEvents.Resized); -} - -static void OnDidMove(id self, SEL cmd, id notification) { - RefreshWindowBounds(); - GLContext_Update(); -} - -static void OnDidBecomeKey(id self, SEL cmd, id notification) { - WindowInfo.Focused = true; - Event_RaiseVoid(&WindowEvents.FocusChanged); -} - -static void OnDidResignKey(id self, SEL cmd, id notification) { - WindowInfo.Focused = false; - Event_RaiseVoid(&WindowEvents.FocusChanged); -} - -static void OnDidMiniaturize(id self, SEL cmd, id notification) { - Event_RaiseVoid(&WindowEvents.StateChanged); -} - -static void OnDidDeminiaturize(id self, SEL cmd, id notification) { - Event_RaiseVoid(&WindowEvents.StateChanged); -} - -static void OnWillClose(id self, SEL cmd, id notification) { - WindowInfo.Exists = false; - Event_RaiseVoid(&WindowEvents.Closing); -} - -/* If this isn't overriden, an annoying beep sound plays anytime a key is pressed */ -static void OnKeyDown(id self, SEL cmd, id ev) { } - -static Class Window_MakeClass(void) { - Class c = objc_allocateClassPair(objc_getClass("NSWindow"), "ClassiCube_Window", 0); - - class_addMethod(c, sel_registerName("windowDidResize:"), OnDidResize, "v@:@"); - class_addMethod(c, sel_registerName("windowDidMove:"), OnDidMove, "v@:@"); - class_addMethod(c, sel_registerName("windowDidBecomeKey:"), OnDidBecomeKey, "v@:@"); - class_addMethod(c, sel_registerName("windowDidResignKey:"), OnDidResignKey, "v@:@"); - class_addMethod(c, sel_registerName("windowDidMiniaturize:"), OnDidMiniaturize, "v@:@"); - class_addMethod(c, sel_registerName("windowDidDeminiaturize:"), OnDidDeminiaturize, "v@:@"); - class_addMethod(c, sel_registerName("windowWillClose:"), OnWillClose, "v@:@"); - class_addMethod(c, sel_registerName("keyDown:"), OnKeyDown, "v@:@"); - - objc_registerClassPair(c); - return c; -} - -/* When the user users left mouse to drag reisze window, this enters 'live resize' mode */ -/* Although the game receives a left mouse down event, it does NOT receive a left mouse up */ -/* This causes the game to get stuck with left mouse down after user finishes resizing */ -/* So work arond that by always releasing left mouse when a live resize is finished */ -static void DidEndLiveResize(id self, SEL cmd) { - Input_SetReleased(KEY_LMOUSE); -} - -static void View_DrawRect(id self, SEL cmd, CGRect r); -static void MakeContentView(void) { - CGRect rect; - id view; - Class c; - - view = objc_msgSend(winHandle, sel_registerName("contentView")); - rect = ((CGRect(*)(id, SEL))(void *)objc_msgSend_stret)(view, selFrame); - - c = objc_allocateClassPair(objc_getClass("NSView"), "ClassiCube_View", 0); - // TODO: test rect is actually correct in View_DrawRect on both 32 and 64 bit -#ifdef __i386__ - class_addMethod(c, sel_registerName("drawRect:"), View_DrawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}"); -#else - class_addMethod(c, sel_registerName("drawRect:"), View_DrawRect, "v@:{NSRect={NSPoint=dd}{NSSize=dd}}"); -#endif - class_addMethod(c, sel_registerName("viewDidEndLiveResize"), DidEndLiveResize, "v@:"); - objc_registerClassPair(c); - - viewHandle = objc_msgSend(c, sel_registerName("alloc")); - objc_msgSend(viewHandle, sel_registerName("initWithFrame:"), rect); - objc_msgSend(winHandle, sel_registerName("setContentView:"), viewHandle); -} - -void Window_Init(void) { - appHandle = objc_msgSend((id)objc_getClass("NSApplication"), sel_registerName("sharedApplication")); - objc_msgSend(appHandle, sel_registerName("activateIgnoringOtherApps:"), true); - Window_CommonInit(); - RegisterSelectors(); -} - -#ifdef CC_BUILD_ICON -extern const int CCIcon_Data[]; -extern const int CCIcon_Width, CCIcon_Height; - -static void ApplyIcon(void) { - CGColorSpaceRef colSpace; - CGDataProviderRef provider; - CGImageRef image; - CGSize size; - void* img; - - colSpace = CGColorSpaceCreateDeviceRGB(); - provider = CGDataProviderCreateWithData(NULL, CCIcon_Data, - Bitmap_DataSize(CCIcon_Width, CCIcon_Height), NULL); - image = CGImageCreate(CCIcon_Width, CCIcon_Height, 8, 32, CCIcon_Width * 4, colSpace, - kCGBitmapByteOrder32Little | kCGImageAlphaLast, provider, NULL, 0, 0); - - size.width = 0; size.height = 0; - img = objc_msgSend((id)objc_getClass("NSImage"), sel_registerName("alloc")); - objc_msgSend(img, sel_registerName("initWithCGImage:size:"), image, size); - objc_msgSend(appHandle, sel_registerName("setApplicationIconImage:"), img); - - /* TODO need to release NSImage here */ - CGImageRelease(image); - CGDataProviderRelease(provider); - CGColorSpaceRelease(colSpace); -} -#else -static void ApplyIcon(void) { } -#endif - -#define NSTitledWindowMask (1 << 0) -#define NSClosableWindowMask (1 << 1) -#define NSMiniaturizableWindowMask (1 << 2) -#define NSResizableWindowMask (1 << 3) -#define NSFullScreenWindowMask (1 << 14) - -void Window_Create(int width, int height) { - Class winClass; - CGRect rect; - - /* Technically the coordinates for the origin are at bottom left corner */ - /* But since the window is in centre of the screen, don't need to care here */ - rect.origin.x = Display_CentreX(width); - rect.origin.y = Display_CentreY(height); - rect.size.width = width; - rect.size.height = height; - - winClass = Window_MakeClass(); - winHandle = objc_msgSend(winClass, sel_registerName("alloc")); - objc_msgSend(winHandle, sel_registerName("initWithContentRect:styleMask:backing:defer:"), rect, (NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask), 0, false); - - Window_CommonCreate(); - objc_msgSend(winHandle, sel_registerName("setDelegate:"), winHandle); - RefreshWindowBounds(); - MakeContentView(); - ApplyIcon(); -} - -void Window_SetTitle(const cc_string* title) { - UInt8 str[NATIVE_STR_LEN]; - CFStringRef titleCF; - int len; - - /* TODO: This leaks memory, old title isn't released */ - len = Platform_EncodeUtf8(str, title); - titleCF = CFStringCreateWithBytes(kCFAllocatorDefault, str, len, kCFStringEncodingUTF8, false); - objc_msgSend(winHandle, sel_registerName("setTitle:"), titleCF); -} - -void Window_Show(void) { - objc_msgSend(winHandle, sel_registerName("makeKeyAndOrderFront:"), appHandle); - RefreshWindowBounds(); // TODO: even necessary? -} - -int Window_GetWindowState(void) { - int flags; - - flags = (int)objc_msgSend(winHandle, sel_registerName("styleMask")); - if (flags & NSFullScreenWindowMask) return WINDOW_STATE_FULLSCREEN; - - flags = (int)objc_msgSend(winHandle, sel_registerName("isMiniaturized")); - return flags ? WINDOW_STATE_MINIMISED : WINDOW_STATE_NORMAL; -} - -// TODO: Only works on 10.7+ -cc_result Window_EnterFullscreen(void) { - objc_msgSend(winHandle, sel_registerName("toggleFullScreen:"), appHandle); - return 0; -} -cc_result Window_ExitFullscreen(void) { - objc_msgSend(winHandle, sel_registerName("toggleFullScreen:"), appHandle); - return 0; -} - -void Window_SetSize(int width, int height) { - /* Can't use setContentSize:, because that resizes from the bottom left corner. */ - CGRect rect = ((CGRect(*)(id, SEL))(void *)objc_msgSend_stret)(winHandle, selFrame); - - rect.origin.y += WindowInfo.Height - height; - rect.size.width += width - WindowInfo.Width; - rect.size.height += height - WindowInfo.Height; - objc_msgSend(winHandle, sel_registerName("setFrame:display:"), rect, true); -} - -void Window_Close(void) { - objc_msgSend(winHandle, sel_registerName("close")); -} - -static int MapNativeMouse(int button) { - if (button == 0) return KEY_LMOUSE; - if (button == 1) return KEY_RMOUSE; - if (button == 2) return KEY_MMOUSE; - return 0; -} - -static void ProcessKeyChars(id ev) { - char buffer[128]; - const char* src; - cc_string str; - id chars; - int i, len, flags; - - /* Ignore text input while cmd is held down */ - /* e.g. so Cmd + V to paste doesn't leave behind 'v' */ - flags = (int)objc_msgSend(ev, selModifiers); - if (flags & 0x000008) return; - if (flags & 0x000010) return; - - chars = objc_msgSend(ev, selCharacters); - src = objc_msgSend(chars, selUtf8String); - len = String_Length(src); - String_InitArray(str, buffer); - - String_AppendUtf8(&str, src, len); - for (i = 0; i < str.length; i++) { - Event_RaiseInt(&InputEvents.Press, str.buffer[i]); - } -} - -static cc_bool GetMouseCoords(int* x, int* y) { - CGPoint loc = Send_CGPoint((id)objc_getClass("NSEvent"), selMouseLoc); - *x = (int)loc.x - windowX; - *y = (DisplayInfo.Height - (int)loc.y) - windowY; - // TODO: this seems to be off by 1 - return *x >= 0 && *y >= 0 && *x < WindowInfo.Width && *y < WindowInfo.Height; -} - -static int TryGetKey(id ev) { - int code = (int)objc_msgSend(ev, selKeycode); - int key = MapNativeKey(code); - if (key) return key; - - Platform_Log1("Unknown key %i", &code); - return 0; -} - -void Window_ProcessEvents(void) { - id ev; - int key, type, steps, x, y; - CGFloat dx, dy; - - for (;;) { - ev = objc_msgSend(appHandle, selNextEvent, 0xFFFFFFFFU, NULL, NSDefaultRunLoopMode, true); - if (!ev) break; - type = (int)objc_msgSend(ev, selType); - - switch (type) { - case 1: /* NSLeftMouseDown */ - case 3: /* NSRightMouseDown */ - case 25: /* NSOtherMouseDown */ - key = MapNativeMouse((int)objc_msgSend(ev, selButton)); - if (GetMouseCoords(&x, &y) && key) Input_SetPressed(key); - break; - - case 2: /* NSLeftMouseUp */ - case 4: /* NSRightMouseUp */ - case 26: /* NSOtherMouseUp */ - key = MapNativeMouse((int)objc_msgSend(ev, selButton)); - if (key) Input_SetReleased(key); - break; - - case 10: /* NSKeyDown */ - key = TryGetKey(ev); - if (key) Input_SetPressed(key); - // TODO: Test works properly with other languages - ProcessKeyChars(ev); - break; - - case 11: /* NSKeyUp */ - key = TryGetKey(ev); - if (key) Input_SetReleased(key); - break; - - case 12: /* NSFlagsChanged */ - key = (int)objc_msgSend(ev, selModifiers); - /* TODO: Figure out how to only get modifiers that changed */ - Input_Set(KEY_LCTRL, key & 0x000001); - Input_Set(KEY_LSHIFT, key & 0x000002); - Input_Set(KEY_RSHIFT, key & 0x000004); - Input_Set(KEY_LWIN, key & 0x000008); - Input_Set(KEY_RWIN, key & 0x000010); - Input_Set(KEY_LALT, key & 0x000020); - Input_Set(KEY_RALT, key & 0x000040); - Input_Set(KEY_RCTRL, key & 0x002000); - Input_Set(KEY_CAPSLOCK, key & 0x010000); - break; - - case 22: /* NSScrollWheel */ - dy = Send_CGFloat(ev, selDeltaY); - /* https://bugs.eclipse.org/bugs/show_bug.cgi?id=220175 */ - /* delta is in 'line height' units, but I don't know how to map that to actual units. */ - /* All I know is that scrolling by '1 wheel notch' produces a delta of around 0.1, and that */ - /* sometimes I'll see it go all the way up to 5-6 with a larger wheel scroll. */ - /* So mulitplying by 10 doesn't really seem a good idea, instead I just round outwards. */ - /* TODO: Figure out if there's a better way than this. */ - steps = dy > 0.0f ? Math_Ceil(dy) : Math_Floor(dy); - Mouse_ScrollWheel(steps); - break; - - case 5: /* NSMouseMoved */ - case 6: /* NSLeftMouseDragged */ - case 7: /* NSRightMouseDragged */ - case 27: /* NSOtherMouseDragged */ - if (GetMouseCoords(&x, &y)) Pointer_SetPosition(0, x, y); - - if (Input_RawMode) { - dx = Send_CGFloat(ev, selDeltaX); - dy = Send_CGFloat(ev, selDeltaY); - Event_RaiseRawMove(&PointerEvents.RawMoved, dx, dy); - } - break; - } - objc_msgSend(appHandle, selSendEvent, ev); - } -} - -static void Cursor_GetRawPos(int* x, int* y) { *x = 0; *y = 0; } -static void ShowDialogCore(const char* title, const char* msg) { - CFStringRef titleCF, msgCF; - id alert; - - alert = objc_msgSend((id)objc_getClass("NSAlert"), sel_registerName("alloc")); - alert = objc_msgSend(alert, sel_registerName("init")); - titleCF = CFStringCreateWithCString(NULL, title, kCFStringEncodingASCII); - msgCF = CFStringCreateWithCString(NULL, msg, kCFStringEncodingASCII); - - objc_msgSend(alert, sel_registerName("setMessageText:"), titleCF); - objc_msgSend(alert, sel_registerName("setInformativeText:"), msgCF); - objc_msgSend(alert, sel_registerName("addButtonWithTitle:"), CFSTR("OK")); - - objc_msgSend(alert, sel_registerName("runModal")); - CFRelease(titleCF); - CFRelease(msgCF); -} - -static struct Bitmap fb_bmp; -void Window_AllocFramebuffer(struct Bitmap* bmp) { - bmp->scan0 = (BitmapCol*)Mem_Alloc(bmp->width * bmp->height, 4, "window pixels"); - fb_bmp = *bmp; -} - -static void View_DrawRect(id self, SEL cmd, CGRect r_) { - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - CGContextRef context = NULL; - CGDataProviderRef provider; - CGImageRef image; - CGRect rect; - id nsContext; - - /* Unfortunately CGImageRef is immutable, so changing the */ - /* underlying data doesn't change what shows when drawing. */ - /* TODO: Find a better way of doing this in cocoa.. */ - if (!fb_bmp.scan0) return; - nsContext = objc_msgSend((id)objc_getClass("NSGraphicsContext"), selCurrentContext); - context = objc_msgSend(nsContext, selGraphicsPort); - - /* TODO: Only update changed bit.. */ - rect.origin.x = 0; rect.origin.y = 0; - rect.size.width = WindowInfo.Width; - rect.size.height = WindowInfo.Height; - - /* TODO: REPLACE THIS AWFUL HACK */ - provider = CGDataProviderCreateWithData(NULL, fb_bmp.scan0, - Bitmap_DataSize(fb_bmp.width, fb_bmp.height), NULL); - image = CGImageCreate(fb_bmp.width, fb_bmp.height, 8, 32, fb_bmp.width * 4, colorSpace, - kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, provider, NULL, 0, 0); - - CGContextDrawImage(context, rect, image); - CGContextSynchronize(context); - - CGImageRelease(image); - CGDataProviderRelease(provider); - CGColorSpaceRelease(colorSpace); -} - -void Window_DrawFramebuffer(Rect2D r) { - CGRect rect; - rect.origin.x = r.X; - rect.origin.y = WindowInfo.Height - r.Y - r.Height; - rect.size.width = r.Width; - rect.size.height = r.Height; - - objc_msgSend(viewHandle, selSetNeedsDisplay, rect); - objc_msgSend(viewHandle, selDisplayIfNeeded); -} - -void Window_FreeFramebuffer(struct Bitmap* bmp) { - Mem_Free(bmp->scan0); -} +/* NOTE: Mostly implemented in interop_cocoa.m */ #endif diff --git a/src/Window.h b/src/Window.h index abd4ab4a0..971a9e7a9 100644 --- a/src/Window.h +++ b/src/Window.h @@ -59,6 +59,8 @@ CC_VAR extern struct _DisplayData { int Display_ScaleX(int x); /* Scales the given Y coordinate from 96 dpi to current display dpi. */ int Display_ScaleY(int y); +#define Display_CentreX(width) (DisplayInfo.X + (DisplayInfo.Width - width) / 2) +#define Display_CentreY(height) (DisplayInfo.Y + (DisplayInfo.Height - height) / 2) /* Data for the game/launcher window. */ CC_VAR extern struct _WinData { diff --git a/src/interop_cocoa.m b/src/interop_cocoa.m index 786816a2d..783751415 100644 --- a/src/interop_cocoa.m +++ b/src/interop_cocoa.m @@ -1,11 +1,447 @@ #include "Logger.h" #include "Platform.h" #include "Window.h" +#include "Input.h" +#include "Event.h" +#include "Bitmap.h" +#include "String.h" #include #include +#include +#include -static NSOpenGLContext* ctx; -extern id viewHandle; +/*########################################################################################################################* +*-------------------------------------------------------Cocoa window------------------------------------------------------* +*#########################################################################################################################*/ +static NSApplication* appHandle; +static NSWindow* winHandle; +static NSView* viewHandle; +extern int windowX, windowY; +extern void Window_CommonCreate(void); +extern void Window_CommonInit(void); +extern int MapNativeKey(UInt32 key); + +static void RefreshWindowBounds(void) { + CGRect win, view; + int viewY; + + win = [winHandle frame]; + view = [viewHandle frame]; + + /* For cocoa, the 0,0 origin is the bottom left corner of windows/views/screen. */ + /* To get window's real Y screen position, first need to find Y of top. (win.y + win.height) */ + /* Then just subtract from screen height to make relative to top instead of bottom of the screen. */ + /* Of course this is only half the story, since we're really after Y position of the content. */ + /* To work out top Y of view relative to window, it's just win.height - (view.y + view.height) */ + viewY = (int)win.size.height - ((int)view.origin.y + (int)view.size.height); + windowX = (int)win.origin.x + (int)view.origin.x; + windowY = DisplayInfo.Height - ((int)win.origin.y + (int)win.size.height) + viewY; + + WindowInfo.Width = (int)view.size.width; + WindowInfo.Height = (int)view.size.height; +} + +static void OnDidResize(id self, SEL cmd, id notification) { + RefreshWindowBounds(); + Event_RaiseVoid(&WindowEvents.Resized); +} + +static void OnDidMove(id self, SEL cmd, id notification) { + RefreshWindowBounds(); + GLContext_Update(); +} + +static void OnDidBecomeKey(id self, SEL cmd, id notification) { + WindowInfo.Focused = true; + Event_RaiseVoid(&WindowEvents.FocusChanged); +} + +static void OnDidResignKey(id self, SEL cmd, id notification) { + WindowInfo.Focused = false; + Event_RaiseVoid(&WindowEvents.FocusChanged); +} + +static void OnDidMiniaturize(id self, SEL cmd, id notification) { + Event_RaiseVoid(&WindowEvents.StateChanged); +} + +static void OnDidDeminiaturize(id self, SEL cmd, id notification) { + Event_RaiseVoid(&WindowEvents.StateChanged); +} + +static void OnWillClose(id self, SEL cmd, id notification) { + WindowInfo.Exists = false; + Event_RaiseVoid(&WindowEvents.Closing); +} + +/* If this isn't overriden, an annoying beep sound plays anytime a key is pressed */ +static void OnKeyDown(id self, SEL cmd, id ev) { } + +static Class Window_MakeClass(void) { + Class c = objc_allocateClassPair(objc_getClass("NSWindow"), "ClassiCube_Window", 0); + + class_addMethod(c, sel_registerName("windowDidResize:"), OnDidResize, "v@:@"); + class_addMethod(c, sel_registerName("windowDidMove:"), OnDidMove, "v@:@"); + class_addMethod(c, sel_registerName("windowDidBecomeKey:"), OnDidBecomeKey, "v@:@"); + class_addMethod(c, sel_registerName("windowDidResignKey:"), OnDidResignKey, "v@:@"); + class_addMethod(c, sel_registerName("windowDidMiniaturize:"), OnDidMiniaturize, "v@:@"); + class_addMethod(c, sel_registerName("windowDidDeminiaturize:"), OnDidDeminiaturize, "v@:@"); + class_addMethod(c, sel_registerName("windowWillClose:"), OnWillClose, "v@:@"); + class_addMethod(c, sel_registerName("keyDown:"), OnKeyDown, "v@:@"); + + objc_registerClassPair(c); + return c; +} + +/* When the user users left mouse to drag reisze window, this enters 'live resize' mode */ +/* Although the game receives a left mouse down event, it does NOT receive a left mouse up */ +/* This causes the game to get stuck with left mouse down after user finishes resizing */ +/* So work arond that by always releasing left mouse when a live resize is finished */ +static void DidEndLiveResize(id self, SEL cmd) { + Input_SetReleased(KEY_LMOUSE); +} + +static void View_DrawRect(id self, SEL cmd, CGRect r); +static void MakeContentView(void) { + CGRect rect; + id view; + Class c; + + view = [winHandle contentView]; + rect = [view frame]; + + c = objc_allocateClassPair(objc_getClass("NSView"), "ClassiCube_View", 0); + // TODO: test rect is actually correct in View_DrawRect on both 32 and 64 bit +#ifdef __i386__ + class_addMethod(c, sel_registerName("drawRect:"), View_DrawRect, "v@:{NSRect={NSPoint=ff}{NSSize=ff}}"); +#else + class_addMethod(c, sel_registerName("drawRect:"), View_DrawRect, "v@:{NSRect={NSPoint=dd}{NSSize=dd}}"); +#endif + class_addMethod(c, sel_registerName("viewDidEndLiveResize"), DidEndLiveResize, "v@:"); + objc_registerClassPair(c); + + viewHandle = [c alloc]; + [viewHandle initWithFrame:rect]; + [winHandle setContentView:viewHandle]; +} + +void Window_Init(void) { + appHandle = [NSApplication sharedApplication]; + [appHandle activateIgnoringOtherApps:true]; + Window_CommonInit(); +} + +#ifdef CC_BUILD_ICON +extern const int CCIcon_Data[]; +extern const int CCIcon_Width, CCIcon_Height; + +static void ApplyIcon(void) { + CGColorSpaceRef colSpace; + CGDataProviderRef provider; + CGImageRef image; + CGSize size; + NSImage* img; + + colSpace = CGColorSpaceCreateDeviceRGB(); + provider = CGDataProviderCreateWithData(NULL, CCIcon_Data, + Bitmap_DataSize(CCIcon_Width, CCIcon_Height), NULL); + image = CGImageCreate(CCIcon_Width, CCIcon_Height, 8, 32, CCIcon_Width * 4, colSpace, + kCGBitmapByteOrder32Little | kCGImageAlphaLast, provider, NULL, 0, 0); + + size.width = 0; size.height = 0; + img = [NSImage alloc]; + [img initWithCGImage:image size:size]; + [appHandle setApplicationIconImage:img]; + + /* TODO need to release NSImage here */ + CGImageRelease(image); + CGDataProviderRelease(provider); + CGColorSpaceRelease(colSpace); +} +#else +static void ApplyIcon(void) { } +#endif + +#define WIN_MASK (NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask) +void Window_Create(int width, int height) { + Class winClass; + CGRect rect; + + /* Technically the coordinates for the origin are at bottom left corner */ + /* But since the window is in centre of the screen, don't need to care here */ + rect.origin.x = Display_CentreX(width); + rect.origin.y = Display_CentreY(height); + rect.size.width = width; + rect.size.height = height; + + winClass = Window_MakeClass(); + winHandle = [winClass alloc]; + [winHandle initWithContentRect:rect styleMask:WIN_MASK backing:0 defer:false]; + + Window_CommonCreate(); + [winHandle setDelegate:winHandle]; + RefreshWindowBounds(); + MakeContentView(); + ApplyIcon(); +} + +void Window_SetTitle(const cc_string* title) { + UInt8 str[NATIVE_STR_LEN]; + CFStringRef titleCF; + int len; + + /* TODO: This leaks memory, old title isn't released */ + len = Platform_EncodeUtf8(str, title); + titleCF = CFStringCreateWithBytes(kCFAllocatorDefault, str, len, kCFStringEncodingUTF8, false); + [winHandle setTitle:titleCF]; +} + +void Window_Show(void) { + [winHandle makeKeyAndOrderFront:appHandle]; + RefreshWindowBounds(); // TODO: even necessary? +} + +int Window_GetWindowState(void) { + int flags; + + flags = [winHandle styleMask]; + if (flags & NSFullScreenWindowMask) return WINDOW_STATE_FULLSCREEN; + + flags = [winHandle isMiniaturized]; + return flags ? WINDOW_STATE_MINIMISED : WINDOW_STATE_NORMAL; +} + +// TODO: Only works on 10.7+ +cc_result Window_EnterFullscreen(void) { + [winHandle toggleFullScreen:appHandle]; + return 0; +} +cc_result Window_ExitFullscreen(void) { + [winHandle toggleFullScreen:appHandle]; + return 0; +} + +void Window_SetSize(int width, int height) { + /* Can't use setContentSize:, because that resizes from the bottom left corner. */ + CGRect rect = [winHandle frame]; + + rect.origin.y += WindowInfo.Height - height; + rect.size.width += width - WindowInfo.Width; + rect.size.height += height - WindowInfo.Height; + [winHandle setFrame:rect display:true]; +} + +void Window_Close(void) { + [winHandle close]; +} + +static int MapNativeMouse(int button) { + if (button == 0) return KEY_LMOUSE; + if (button == 1) return KEY_RMOUSE; + if (button == 2) return KEY_MMOUSE; + return 0; +} + +static void ProcessKeyChars(id ev) { + char buffer[128]; + const char* src; + cc_string str; + id chars; + int i, len, flags; + + /* Ignore text input while cmd is held down */ + /* e.g. so Cmd + V to paste doesn't leave behind 'v' */ + flags = [ev modifierFlags]; + if (flags & 0x000008) return; + if (flags & 0x000010) return; + + chars = [ev characters]; + src = [chars UTF8String]; + len = String_Length(src); + String_InitArray(str, buffer); + + String_AppendUtf8(&str, src, len); + for (i = 0; i < str.length; i++) { + Event_RaiseInt(&InputEvents.Press, str.buffer[i]); + } +} + +static cc_bool GetMouseCoords(int* x, int* y) { + CGPoint loc = [NSEvent mouseLocation]; + *x = (int)loc.x - windowX; + *y = (DisplayInfo.Height - (int)loc.y) - windowY; + // TODO: this seems to be off by 1 + return *x >= 0 && *y >= 0 && *x < WindowInfo.Width && *y < WindowInfo.Height; +} + +static int TryGetKey(NSEvent* ev) { + int code = [ev keyCode]; + int key = MapNativeKey(code); + if (key) return key; + + Platform_Log1("Unknown key %i", &code); + return 0; +} + +void Window_ProcessEvents(void) { + NSEvent* ev; + int key, type, steps, x, y; + CGFloat dx, dy; + + for (;;) { + ev = [appHandle nextEventMatchingMask:0xFFFFFFFFU untilDate:NULL inMode:NSDefaultRunLoopMode dequeue:true]; + if (!ev) break; + type = [ev type]; + + switch (type) { + case 1: /* NSLeftMouseDown */ + case 3: /* NSRightMouseDown */ + case 25: /* NSOtherMouseDown */ + key = MapNativeMouse([ev buttonNumber]); + if (GetMouseCoords(&x, &y) && key) Input_SetPressed(key); + break; + + case 2: /* NSLeftMouseUp */ + case 4: /* NSRightMouseUp */ + case 26: /* NSOtherMouseUp */ + key = MapNativeMouse([ev buttonNumber]); + if (key) Input_SetReleased(key); + break; + + case 10: /* NSKeyDown */ + key = TryGetKey(ev); + if (key) Input_SetPressed(key); + // TODO: Test works properly with other languages + ProcessKeyChars(ev); + break; + + case 11: /* NSKeyUp */ + key = TryGetKey(ev); + if (key) Input_SetReleased(key); + break; + + case 12: /* NSFlagsChanged */ + key = [ev modifierFlags]; + /* TODO: Figure out how to only get modifiers that changed */ + Input_Set(KEY_LCTRL, key & 0x000001); + Input_Set(KEY_LSHIFT, key & 0x000002); + Input_Set(KEY_RSHIFT, key & 0x000004); + Input_Set(KEY_LWIN, key & 0x000008); + Input_Set(KEY_RWIN, key & 0x000010); + Input_Set(KEY_LALT, key & 0x000020); + Input_Set(KEY_RALT, key & 0x000040); + Input_Set(KEY_RCTRL, key & 0x002000); + Input_Set(KEY_CAPSLOCK, key & 0x010000); + break; + + case 22: /* NSScrollWheel */ + dy = [ev deltaY]; + /* https://bugs.eclipse.org/bugs/show_bug.cgi?id=220175 */ + /* delta is in 'line height' units, but I don't know how to map that to actual units. */ + /* All I know is that scrolling by '1 wheel notch' produces a delta of around 0.1, and that */ + /* sometimes I'll see it go all the way up to 5-6 with a larger wheel scroll. */ + /* So mulitplying by 10 doesn't really seem a good idea, instead I just round outwards. */ + /* TODO: Figure out if there's a better way than this. */ + steps = dy > 0.0f ? Math_Ceil(dy) : Math_Floor(dy); + Mouse_ScrollWheel(steps); + break; + + case 5: /* NSMouseMoved */ + case 6: /* NSLeftMouseDragged */ + case 7: /* NSRightMouseDragged */ + case 27: /* NSOtherMouseDragged */ + if (GetMouseCoords(&x, &y)) Pointer_SetPosition(0, x, y); + + if (Input_RawMode) { + dx = [ev deltaX]; + dy = [ev deltaY]; + Event_RaiseRawMove(&PointerEvents.RawMoved, dx, dy); + } + break; + } + [appHandle sendEvent:ev]; + } +} + +void Cursor_GetRawPos(int* x, int* y) { *x = 0; *y = 0; } +void ShowDialogCore(const char* title, const char* msg) { + CFStringRef titleCF, msgCF; + NSAlert* alert; + + alert = [NSAlert alloc]; + alert = [alert init]; + titleCF = CFStringCreateWithCString(NULL, title, kCFStringEncodingASCII); + msgCF = CFStringCreateWithCString(NULL, msg, kCFStringEncodingASCII); + + [alert setMessageText: titleCF]; + [alert setInformativeText: msgCF]; + [alert addButtonWithTitle: CFSTR("OK")]; + + [alert runModal]; + CFRelease(titleCF); + CFRelease(msgCF); +} + +static struct Bitmap fb_bmp; +void Window_AllocFramebuffer(struct Bitmap* bmp) { + bmp->scan0 = (BitmapCol*)Mem_Alloc(bmp->width * bmp->height, 4, "window pixels"); + fb_bmp = *bmp; +} + +static void View_DrawRect(id self, SEL cmd, CGRect r_) { + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef context = NULL; + CGDataProviderRef provider; + CGImageRef image; + CGRect rect; + id nsContext; + + /* Unfortunately CGImageRef is immutable, so changing the */ + /* underlying data doesn't change what shows when drawing. */ + /* TODO: Find a better way of doing this in cocoa.. */ + if (!fb_bmp.scan0) return; + nsContext = [NSGraphicsContext currentContext]; + context = [nsContext graphicsPort]; + + /* TODO: Only update changed bit.. */ + rect.origin.x = 0; rect.origin.y = 0; + rect.size.width = WindowInfo.Width; + rect.size.height = WindowInfo.Height; + + /* TODO: REPLACE THIS AWFUL HACK */ + provider = CGDataProviderCreateWithData(NULL, fb_bmp.scan0, + Bitmap_DataSize(fb_bmp.width, fb_bmp.height), NULL); + image = CGImageCreate(fb_bmp.width, fb_bmp.height, 8, 32, fb_bmp.width * 4, colorSpace, + kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, provider, NULL, 0, 0); + + CGContextDrawImage(context, rect, image); + CGContextSynchronize(context); + + CGImageRelease(image); + CGDataProviderRelease(provider); + CGColorSpaceRelease(colorSpace); +} + +void Window_DrawFramebuffer(Rect2D r) { + CGRect rect; + rect.origin.x = r.X; + rect.origin.y = WindowInfo.Height - r.Y - r.Height; + rect.size.width = r.Width; + rect.size.height = r.Height; + + [viewHandle setNeedsDisplayInRect:rect]; + [viewHandle displayIfNeeded]; +} + +void Window_FreeFramebuffer(struct Bitmap* bmp) { + Mem_Free(bmp->scan0); +} + + +/*########################################################################################################################* +*--------------------------------------------------------NSOpenGL---------------------------------------------------------* +*#########################################################################################################################*/ +static NSOpenGLContext* ctxHandle; static NSOpenGLPixelFormat* MakePixelFormat(cc_bool fullscreen) { NSOpenGLPixelFormatAttribute attribs[7] = { @@ -29,33 +465,33 @@ void GLContext_Create(void) { } if (!fmt) Logger_Abort("Choosing pixel format"); - ctx = [NSOpenGLContext alloc]; - ctx = [ctx initWithFormat:fmt shareContext:NULL]; - if (!ctx) Logger_Abort("Failed to create OpenGL context"); + ctxHandle = [NSOpenGLContext alloc]; + ctxHandle = [ctxHandle initWithFormat:fmt shareContext:NULL]; + if (!ctxHandle) Logger_Abort("Failed to create OpenGL context"); - [ctx setView:viewHandle]; + [ctxHandle setView:viewHandle]; [fmt release]; - [ctx makeCurrentContext]; - [ctx update]; + [ctxHandle makeCurrentContext]; + [ctxHandle update]; } void GLContext_Update(void) { // TODO: Why does this crash on resizing - [ctx update]; + [ctxHandle update]; } void GLContext_Free(void) { [NSOpenGLContext clearCurrentContext]; - [ctx clearDrawable]; - [ctx release]; + [ctxHandle clearDrawable]; + [ctxHandle release]; } cc_bool GLContext_SwapBuffers(void) { - [ctx flushBuffer]; + [ctxHandle flushBuffer]; return true; } void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) { int value = vsync ? 1 : 0; - [ctx setValues:&value forParameter: NSOpenGLContextParameterSwapInterval]; + [ctxHandle setValues:&value forParameter: NSOpenGLContextParameterSwapInterval]; }