Port most of cocoa window back to ObjC

This commit is contained in:
UnknownShadow200 2021-01-23 10:46:28 +11:00
parent 7f0a23674c
commit a58b0660a7
3 changed files with 472 additions and 503 deletions

View File

@ -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 <ApplicationServices/ApplicationServices.h>
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 <objc/message.h>
#include <objc/runtime.h>
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

View File

@ -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 {

View File

@ -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 <Cocoa/Cocoa.h>
#include <OpenGL/OpenGL.h>
#include <objc/message.h>
#include <objc/runtime.h>
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];
}