mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-09-13 09:35:23 -04:00
Port most of cocoa window back to ObjC
This commit is contained in:
parent
7f0a23674c
commit
a58b0660a7
511
src/Window.c
511
src/Window.c
@ -15,8 +15,6 @@ struct _WinData WindowInfo;
|
|||||||
|
|
||||||
int Display_ScaleX(int x) { return (int)(x * DisplayInfo.ScaleX); }
|
int Display_ScaleX(int x) { return (int)(x * DisplayInfo.ScaleX); }
|
||||||
int Display_ScaleY(int y) { return (int)(y * DisplayInfo.ScaleY); }
|
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
|
#ifndef CC_BUILD_WEB
|
||||||
void Clipboard_RequestText(RequestClipboardCallback callback, void* obj) {
|
void Clipboard_RequestText(RequestClipboardCallback callback, void* obj) {
|
||||||
@ -28,20 +26,29 @@ void Clipboard_RequestText(RequestClipboardCallback callback, void* obj) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CC_BUILD_IOS
|
#if defined CC_BUILD_IOS
|
||||||
/* iOS implements some functions in external interop_ios.m file */
|
/* iOS implements these functions in external interop_ios.m file */
|
||||||
#define CC_MAYBE_OBJC extern
|
#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
|
#else
|
||||||
/* All other platforms implement internally in this file */
|
/* 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
|
#endif
|
||||||
|
|
||||||
|
|
||||||
static int cursorPrevX, cursorPrevY;
|
static int cursorPrevX, cursorPrevY;
|
||||||
static cc_bool cursorVisible = true;
|
static cc_bool cursorVisible = true;
|
||||||
/* Gets the position of the cursor in screen or window coordinates. */
|
/* Gets the position of the cursor in screen or window coordinates. */
|
||||||
CC_MAYBE_OBJC void Cursor_GetRawPos(int* x, int* y);
|
CC_MAYBE_OBJC1 void Cursor_GetRawPos(int* x, int* y);
|
||||||
CC_MAYBE_OBJC void Cursor_DoSetVisible(cc_bool visible);
|
CC_MAYBE_OBJC2 void Cursor_DoSetVisible(cc_bool visible);
|
||||||
|
|
||||||
void Cursor_SetVisible(cc_bool visible) {
|
void Cursor_SetVisible(cc_bool visible) {
|
||||||
if (cursorVisible == visible) return;
|
if (cursorVisible == visible) return;
|
||||||
@ -80,7 +87,7 @@ static void DefaultDisableRawMouse(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* The actual windowing system specific method to display a message box */
|
/* 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) {
|
void Window_ShowDialog(const char* title, const char* msg) {
|
||||||
/* Ensure cursor is visible while showing message box */
|
/* Ensure cursor is visible while showing message box */
|
||||||
cc_bool visible = cursorVisible;
|
cc_bool visible = cursorVisible;
|
||||||
@ -2087,9 +2094,9 @@ void Window_DisableRawMouse(void) {
|
|||||||
*#########################################################################################################################*/
|
*#########################################################################################################################*/
|
||||||
#elif defined CC_BUILD_CARBON || defined CC_BUILD_COCOA
|
#elif defined CC_BUILD_CARBON || defined CC_BUILD_COCOA
|
||||||
#include <ApplicationServices/ApplicationServices.h>
|
#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();
|
CGDirectDisplayID display = CGMainDisplayID();
|
||||||
CGRect bounds = CGDisplayBounds(display);
|
CGRect bounds = CGDisplayBounds(display);
|
||||||
|
|
||||||
@ -2107,7 +2114,7 @@ static pascal OSErr HandleQuitMessage(const AppleEvent* ev, AppleEvent* reply, l
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Window_CommonCreate(void) {
|
CC_OBJC_VISIBLE void Window_CommonCreate(void) {
|
||||||
WindowInfo.Exists = true;
|
WindowInfo.Exists = true;
|
||||||
/* for quit buttons in dock and menubar */
|
/* for quit buttons in dock and menubar */
|
||||||
AEInstallEventHandler(kCoreEventClass, kAEQuitApplication,
|
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,
|
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,
|
'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.. */
|
/* TODO: Check these.. */
|
||||||
/* case 0x37: return KEY_LWIN; */
|
/* case 0x37: return KEY_LWIN; */
|
||||||
/* case 0x38: return KEY_LSHIFT; */
|
/* case 0x38: return KEY_LSHIFT; */
|
||||||
@ -2735,483 +2742,7 @@ void Window_FreeFramebuffer(struct Bitmap* bmp) {
|
|||||||
*-------------------------------------------------------Cocoa window------------------------------------------------------*
|
*-------------------------------------------------------Cocoa window------------------------------------------------------*
|
||||||
*#########################################################################################################################*/
|
*#########################################################################################################################*/
|
||||||
#elif defined CC_BUILD_COCOA
|
#elif defined CC_BUILD_COCOA
|
||||||
#include <objc/message.h>
|
/* NOTE: Mostly implemented in interop_cocoa.m */
|
||||||
#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);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,6 +59,8 @@ CC_VAR extern struct _DisplayData {
|
|||||||
int Display_ScaleX(int x);
|
int Display_ScaleX(int x);
|
||||||
/* Scales the given Y coordinate from 96 dpi to current display dpi. */
|
/* Scales the given Y coordinate from 96 dpi to current display dpi. */
|
||||||
int Display_ScaleY(int y);
|
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. */
|
/* Data for the game/launcher window. */
|
||||||
CC_VAR extern struct _WinData {
|
CC_VAR extern struct _WinData {
|
||||||
|
@ -1,11 +1,447 @@
|
|||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
#include "Platform.h"
|
#include "Platform.h"
|
||||||
#include "Window.h"
|
#include "Window.h"
|
||||||
|
#include "Input.h"
|
||||||
|
#include "Event.h"
|
||||||
|
#include "Bitmap.h"
|
||||||
|
#include "String.h"
|
||||||
#include <Cocoa/Cocoa.h>
|
#include <Cocoa/Cocoa.h>
|
||||||
#include <OpenGL/OpenGL.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) {
|
static NSOpenGLPixelFormat* MakePixelFormat(cc_bool fullscreen) {
|
||||||
NSOpenGLPixelFormatAttribute attribs[7] = {
|
NSOpenGLPixelFormatAttribute attribs[7] = {
|
||||||
@ -29,33 +465,33 @@ void GLContext_Create(void) {
|
|||||||
}
|
}
|
||||||
if (!fmt) Logger_Abort("Choosing pixel format");
|
if (!fmt) Logger_Abort("Choosing pixel format");
|
||||||
|
|
||||||
ctx = [NSOpenGLContext alloc];
|
ctxHandle = [NSOpenGLContext alloc];
|
||||||
ctx = [ctx initWithFormat:fmt shareContext:NULL];
|
ctxHandle = [ctxHandle initWithFormat:fmt shareContext:NULL];
|
||||||
if (!ctx) Logger_Abort("Failed to create OpenGL context");
|
if (!ctxHandle) Logger_Abort("Failed to create OpenGL context");
|
||||||
|
|
||||||
[ctx setView:viewHandle];
|
[ctxHandle setView:viewHandle];
|
||||||
[fmt release];
|
[fmt release];
|
||||||
[ctx makeCurrentContext];
|
[ctxHandle makeCurrentContext];
|
||||||
[ctx update];
|
[ctxHandle update];
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLContext_Update(void) {
|
void GLContext_Update(void) {
|
||||||
// TODO: Why does this crash on resizing
|
// TODO: Why does this crash on resizing
|
||||||
[ctx update];
|
[ctxHandle update];
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLContext_Free(void) {
|
void GLContext_Free(void) {
|
||||||
[NSOpenGLContext clearCurrentContext];
|
[NSOpenGLContext clearCurrentContext];
|
||||||
[ctx clearDrawable];
|
[ctxHandle clearDrawable];
|
||||||
[ctx release];
|
[ctxHandle release];
|
||||||
}
|
}
|
||||||
|
|
||||||
cc_bool GLContext_SwapBuffers(void) {
|
cc_bool GLContext_SwapBuffers(void) {
|
||||||
[ctx flushBuffer];
|
[ctxHandle flushBuffer];
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
|
void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
|
||||||
int value = vsync ? 1 : 0;
|
int value = vsync ? 1 : 0;
|
||||||
[ctx setValues:&value forParameter: NSOpenGLContextParameterSwapInterval];
|
[ctxHandle setValues:&value forParameter: NSOpenGLContextParameterSwapInterval];
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user