Split up Window.c into separate backends, except for mac

This commit is contained in:
UnknownShadow200 2021-07-16 07:57:17 +10:00
parent bf64700292
commit df6cc32cdf
11 changed files with 3462 additions and 3436 deletions

View File

@ -318,6 +318,11 @@
<ClCompile Include="Widgets.c" />
<ClCompile Include="Logger.c" />
<ClCompile Include="Window.c" />
<ClCompile Include="Window_Android.c" />
<ClCompile Include="Window_SDL.c" />
<ClCompile Include="Window_Web.c" />
<ClCompile Include="Window_Win.c" />
<ClCompile Include="Window_X11.c" />
<ClCompile Include="World.c" />
<ClCompile Include="_autofit.c" />
<ClCompile Include="_cff.c" />

View File

@ -566,5 +566,20 @@
<ClCompile Include="Http_Worker.c">
<Filter>Source Files\Network</Filter>
</ClCompile>
<ClCompile Include="Window_SDL.c">
<Filter>Source Files\Platform</Filter>
</ClCompile>
<ClCompile Include="Window_Web.c">
<Filter>Source Files\Platform</Filter>
</ClCompile>
<ClCompile Include="Window_Android.c">
<Filter>Source Files\Platform</Filter>
</ClCompile>
<ClCompile Include="Window_X11.c">
<Filter>Source Files\Platform</Filter>
</ClCompile>
<ClCompile Include="Window_Win.c">
<Filter>Source Files\Platform</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@ -154,7 +154,6 @@ Thus it is **NOT SAFE** to allocate a string on the stack. */
#define CC_BUILD_WIN
#define CC_BUILD_D3D9
#define CC_BUILD_WINGUI
#define CC_BUILD_WGL
#define CC_BUILD_WININET
#define CC_BUILD_WINMM
#elif defined __ANDROID__

File diff suppressed because it is too large Load Diff

410
src/Window_Android.c Normal file
View File

@ -0,0 +1,410 @@
#include "Core.h"
#if defined CC_BUILD_ANDROID && !defined CC_BUILD_SDL
#include "_WindowBase.h"
#include "String.h"
#include "Funcs.h"
#include "Bitmap.h"
#include "Errors.h"
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include <android/keycodes.h>
static ANativeWindow* win_handle;
static void RefreshWindowBounds(void) {
WindowInfo.Width = ANativeWindow_getWidth(win_handle);
WindowInfo.Height = ANativeWindow_getHeight(win_handle);
Platform_Log2("SCREEN BOUNDS: %i,%i", &WindowInfo.Width, &WindowInfo.Height);
Event_RaiseVoid(&WindowEvents.Resized);
}
static int MapNativeKey(int code) {
if (code >= AKEYCODE_0 && code <= AKEYCODE_9) return (code - AKEYCODE_0) + '0';
if (code >= AKEYCODE_A && code <= AKEYCODE_Z) return (code - AKEYCODE_A) + 'A';
if (code >= AKEYCODE_F1 && code <= AKEYCODE_F12) return (code - AKEYCODE_F1) + KEY_F1;
if (code >= AKEYCODE_NUMPAD_0 && code <= AKEYCODE_NUMPAD_9) return (code - AKEYCODE_NUMPAD_0) + KEY_KP0;
switch (code) {
/* TODO: AKEYCODE_STAR */
/* TODO: AKEYCODE_POUND */
case AKEYCODE_BACK: return KEY_ESCAPE;
case AKEYCODE_COMMA: return KEY_COMMA;
case AKEYCODE_PERIOD: return KEY_PERIOD;
case AKEYCODE_ALT_LEFT: return KEY_LALT;
case AKEYCODE_ALT_RIGHT: return KEY_RALT;
case AKEYCODE_SHIFT_LEFT: return KEY_LSHIFT;
case AKEYCODE_SHIFT_RIGHT: return KEY_RSHIFT;
case AKEYCODE_TAB: return KEY_TAB;
case AKEYCODE_SPACE: return KEY_SPACE;
case AKEYCODE_ENTER: return KEY_ENTER;
case AKEYCODE_DEL: return KEY_BACKSPACE;
case AKEYCODE_GRAVE: return KEY_TILDE;
case AKEYCODE_MINUS: return KEY_MINUS;
case AKEYCODE_EQUALS: return KEY_EQUALS;
case AKEYCODE_LEFT_BRACKET: return KEY_LBRACKET;
case AKEYCODE_RIGHT_BRACKET: return KEY_RBRACKET;
case AKEYCODE_BACKSLASH: return KEY_BACKSLASH;
case AKEYCODE_SEMICOLON: return KEY_SEMICOLON;
case AKEYCODE_APOSTROPHE: return KEY_QUOTE;
case AKEYCODE_SLASH: return KEY_SLASH;
/* TODO: AKEYCODE_AT */
/* TODO: AKEYCODE_PLUS */
/* TODO: AKEYCODE_MENU */
case AKEYCODE_PAGE_UP: return KEY_PAGEUP;
case AKEYCODE_PAGE_DOWN: return KEY_PAGEDOWN;
case AKEYCODE_ESCAPE: return KEY_ESCAPE;
case AKEYCODE_FORWARD_DEL: return KEY_DELETE;
case AKEYCODE_CTRL_LEFT: return KEY_LCTRL;
case AKEYCODE_CTRL_RIGHT: return KEY_RCTRL;
case AKEYCODE_CAPS_LOCK: return KEY_CAPSLOCK;
case AKEYCODE_SCROLL_LOCK: return KEY_SCROLLLOCK;
case AKEYCODE_META_LEFT: return KEY_LWIN;
case AKEYCODE_META_RIGHT: return KEY_RWIN;
case AKEYCODE_SYSRQ: return KEY_PRINTSCREEN;
case AKEYCODE_BREAK: return KEY_PAUSE;
case AKEYCODE_INSERT: return KEY_INSERT;
case AKEYCODE_NUM_LOCK: return KEY_NUMLOCK;
case AKEYCODE_NUMPAD_DIVIDE: return KEY_KP_DIVIDE;
case AKEYCODE_NUMPAD_MULTIPLY: return KEY_KP_MULTIPLY;
case AKEYCODE_NUMPAD_SUBTRACT: return KEY_KP_MINUS;
case AKEYCODE_NUMPAD_ADD: return KEY_KP_PLUS;
case AKEYCODE_NUMPAD_DOT: return KEY_KP_DECIMAL;
case AKEYCODE_NUMPAD_ENTER: return KEY_KP_ENTER;
}
return KEY_NONE;
}
static void JNICALL java_processKeyDown(JNIEnv* env, jobject o, jint code) {
int key = MapNativeKey(code);
Platform_Log2("KEY - DOWN %i,%i", &code, &key);
if (key) Input_SetPressed(key);
}
static void JNICALL java_processKeyUp(JNIEnv* env, jobject o, jint code) {
int key = MapNativeKey(code);
Platform_Log2("KEY - UP %i,%i", &code, &key);
if (key) Input_SetReleased(key);
}
static void JNICALL java_processKeyChar(JNIEnv* env, jobject o, jint code) {
char keyChar;
int key = MapNativeKey(code);
Platform_Log2("KEY - PRESS %i,%i", &code, &key);
if (Convert_TryCodepointToCP437(code, &keyChar)) {
Event_RaiseInt(&InputEvents.Press, keyChar);
}
}
static void JNICALL java_processKeyText(JNIEnv* env, jobject o, jstring str) {
char buffer[NATIVE_STR_LEN];
cc_string text = JavaGetString(env, str, buffer);
Platform_Log1("KEY - TEXT %s", &text);
Event_RaiseString(&InputEvents.TextChanged, &text);
}
static void JNICALL java_processPointerDown(JNIEnv* env, jobject o, jint id, jint x, jint y, jint isMouse) {
Platform_Log4("POINTER %i (%i) - DOWN %i,%i", &id, &isMouse, &x, &y);
Input_AddTouch(id, x, y);
}
static void JNICALL java_processPointerUp(JNIEnv* env, jobject o, jint id, jint x, jint y, jint isMouse) {
Platform_Log4("POINTER %i (%i) - UP %i,%i", &id, &isMouse, &x, &y);
Input_RemoveTouch(id, x, y);
}
static void JNICALL java_processPointerMove(JNIEnv* env, jobject o, jint id, jint x, jint y, jint isMouse) {
Platform_Log4("POINTER %i (%i) - MOVE %i,%i", &id, &isMouse, &x, &y);
Input_UpdateTouch(id, x, y);
}
static void JNICALL java_processSurfaceCreated(JNIEnv* env, jobject o, jobject surface) {
Platform_LogConst("WIN - CREATED");
win_handle = ANativeWindow_fromSurface(env, surface);
WindowInfo.Handle = win_handle;
RefreshWindowBounds();
/* TODO: Restore context */
Event_RaiseVoid(&WindowEvents.Created);
}
#include "Graphics.h"
static void JNICALL java_processSurfaceDestroyed(JNIEnv* env, jobject o) {
Platform_LogConst("WIN - DESTROYED");
if (win_handle) ANativeWindow_release(win_handle);
win_handle = NULL;
WindowInfo.Handle = NULL;
/* eglSwapBuffers might return EGL_BAD_SURFACE, EGL_BAD_ALLOC, or some other error */
/* Instead the context is lost here in a consistent manner */
if (Gfx.Created) Gfx_LoseContext("surface lost");
JavaCallVoid(env, "processedSurfaceDestroyed", "()V", NULL);
}
static void JNICALL java_processSurfaceResized(JNIEnv* env, jobject o, jobject surface) {
Platform_LogConst("WIN - RESIZED");
RefreshWindowBounds();
}
static void JNICALL java_processSurfaceRedrawNeeded(JNIEnv* env, jobject o) {
Platform_LogConst("WIN - REDRAW");
Event_RaiseVoid(&WindowEvents.Redraw);
}
static void JNICALL java_onStart(JNIEnv* env, jobject o) {
Platform_LogConst("APP - ON START");
}
static void JNICALL java_onStop(JNIEnv* env, jobject o) {
Platform_LogConst("APP - ON STOP");
}
static void JNICALL java_onResume(JNIEnv* env, jobject o) {
Platform_LogConst("APP - ON RESUME");
/* TODO: Resume rendering */
}
static void JNICALL java_onPause(JNIEnv* env, jobject o) {
Platform_LogConst("APP - ON PAUSE");
/* TODO: Disable rendering */
}
static void JNICALL java_onDestroy(JNIEnv* env, jobject o) {
Platform_LogConst("APP - ON DESTROY");
if (WindowInfo.Exists) Window_Close();
/* TODO: signal to java code we're done */
JavaCallVoid(env, "processedDestroyed", "()V", NULL);
}
static void JNICALL java_onGotFocus(JNIEnv* env, jobject o) {
Platform_LogConst("APP - GOT FOCUS");
WindowInfo.Focused = true;
Event_RaiseVoid(&WindowEvents.FocusChanged);
}
static void JNICALL java_onLostFocus(JNIEnv* env, jobject o) {
Platform_LogConst("APP - LOST FOCUS");
WindowInfo.Focused = false;
Event_RaiseVoid(&WindowEvents.FocusChanged);
/* TODO: Disable rendering? */
}
static void JNICALL java_onLowMemory(JNIEnv* env, jobject o) {
Platform_LogConst("APP - LOW MEM");
/* TODO: Low memory */
}
static const JNINativeMethod methods[19] = {
{ "processKeyDown", "(I)V", java_processKeyDown },
{ "processKeyUp", "(I)V", java_processKeyUp },
{ "processKeyChar", "(I)V", java_processKeyChar },
{ "processKeyText", "(Ljava/lang/String;)V", java_processKeyText },
{ "processPointerDown", "(IIII)V", java_processPointerDown },
{ "processPointerUp", "(IIII)V", java_processPointerUp },
{ "processPointerMove", "(IIII)V", java_processPointerMove },
{ "processSurfaceCreated", "(Landroid/view/Surface;)V", java_processSurfaceCreated },
{ "processSurfaceDestroyed", "()V", java_processSurfaceDestroyed },
{ "processSurfaceResized", "(Landroid/view/Surface;)V", java_processSurfaceResized },
{ "processSurfaceRedrawNeeded", "()V", java_processSurfaceRedrawNeeded },
{ "processOnStart", "()V", java_onStart },
{ "processOnStop", "()V", java_onStop },
{ "processOnResume", "()V", java_onResume },
{ "processOnPause", "()V", java_onPause },
{ "processOnDestroy", "()V", java_onDestroy },
{ "processOnGotFocus", "()V", java_onGotFocus },
{ "processOnLostFocus", "()V", java_onLostFocus },
{ "processOnLowMemory", "()V", java_onLowMemory }
};
void Window_Init(void) {
JNIEnv* env;
/* TODO: ANativeActivity_setWindowFlags(app->activity, AWINDOW_FLAG_FULLSCREEN, 0); */
JavaGetCurrentEnv(env);
JavaRegisterNatives(env, methods);
WindowInfo.SoftKeyboard = SOFT_KEYBOARD_RESIZE;
Input_SetTouchMode(true);
DisplayInfo.Depth = 32;
DisplayInfo.ScaleX = JavaCallFloat(env, "getDpiX", "()F", NULL);
DisplayInfo.ScaleY = JavaCallFloat(env, "getDpiY", "()F", NULL);
}
void Window_Create(int width, int height) {
WindowInfo.Exists = true;
/* actual window creation is done when processSurfaceCreated is received */
}
static cc_bool winCreated;
static void OnWindowCreated(void* obj) { winCreated = true; }
cc_bool Window_RemakeSurface(void) {
JNIEnv* env;
JavaGetCurrentEnv(env);
winCreated = false;
/* Force window to be destroyed and re-created */
/* (see comments in setupForGame for why this has to be done) */
JavaCallVoid(env, "setupForGame", "()V", NULL);
Event_Register_(&WindowEvents.Created, NULL, OnWindowCreated);
Platform_LogConst("Entering wait for window exist loop..");
/* Loop until window gets created async */
while (WindowInfo.Exists && !winCreated) {
Window_ProcessEvents();
Thread_Sleep(10);
}
Platform_LogConst("OK window created..");
Event_Unregister_(&WindowEvents.Created, NULL, OnWindowCreated);
return winCreated;
}
void Window_SetTitle(const cc_string* title) {
/* TODO: Implement this somehow */
/* Maybe https://stackoverflow.com/questions/2198410/how-to-change-title-of-activity-in-android */
}
void Clipboard_GetText(cc_string* value) {
JavaCall_Void_String("getClipboardText", value);
}
void Clipboard_SetText(const cc_string* value) {
JavaCall_String_Void("setClipboardText", value);
}
void Window_Show(void) { } /* Window already visible */
int Window_GetWindowState(void) {
JNIEnv* env;
JavaGetCurrentEnv(env);
return JavaCallInt(env, "getWindowState", "()I", NULL);
}
cc_result Window_EnterFullscreen(void) {
JNIEnv* env;
JavaGetCurrentEnv(env);
JavaCallVoid(env, "enterFullscreen", "()V", NULL);
return 0;
}
cc_result Window_ExitFullscreen(void) {
JNIEnv* env;
JavaGetCurrentEnv(env);
JavaCallVoid(env, "exitFullscreen", "()V", NULL);
return 0;
}
void Window_SetSize(int width, int height) { }
void Window_Close(void) {
WindowInfo.Exists = false;
Event_RaiseVoid(&WindowEvents.Closing);
/* TODO: Do we need to call finish here */
/* ANativeActivity_finish(app->activity); */
}
void Window_ProcessEvents(void) {
JNIEnv* env;
JavaGetCurrentEnv(env);
/* TODO: Cache the java env and cache the method ID!!!!! */
JavaCallVoid(env, "processEvents", "()V", NULL);
}
/* No actual mouse cursor */
static void Cursor_GetRawPos(int* x, int* y) { *x = 0; *y = 0; }
void Cursor_SetPosition(int x, int y) { }
static void Cursor_DoSetVisible(cc_bool visible) { }
static void ShowDialogCore(const char* title, const char* msg) {
JNIEnv* env;
jvalue args[2];
JavaGetCurrentEnv(env);
Platform_LogConst(title);
Platform_LogConst(msg);
/* in case surface destroyed message has arrived */
Window_ProcessEvents();
args[0].l = JavaMakeConst(env, title);
args[1].l = JavaMakeConst(env, msg);
JavaCallVoid(env, "showAlert", "(Ljava/lang/String;Ljava/lang/String;)V", args);
(*env)->DeleteLocalRef(env, args[0].l);
(*env)->DeleteLocalRef(env, args[1].l);
}
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;
}
void Window_DrawFramebuffer(Rect2D r) {
ANativeWindow_Buffer buffer;
cc_uint32* src;
cc_uint32* dst;
ARect b;
int y, res, size;
/* window not created yet */
if (!win_handle) return;
b.left = r.X; b.right = r.X + r.Width;
b.top = r.Y; b.bottom = r.Y + r.Height;
/* Platform_Log4("DIRTY: %i,%i - %i,%i", &b.left, &b.top, &b.right, &b.bottom); */
res = ANativeWindow_lock(win_handle, &buffer, &b);
if (res) Logger_Abort2(res, "Locking window pixels");
/* Platform_Log4("ADJUS: %i,%i - %i,%i", &b.left, &b.top, &b.right, &b.bottom); */
/* In some rare cases, the returned locked region will be entire area of the surface */
/* This can cause a crash if the surface has been resized (i.e. device rotated), */
/* but the framebuffer has not been resized yet. So always constrain bounds. */
b.left = min(b.left, fb_bmp.width); b.right = min(b.right, fb_bmp.width);
b.top = min(b.top, fb_bmp.height); b.bottom = min(b.bottom, fb_bmp.height);
src = (cc_uint32*)fb_bmp.scan0 + b.left;
dst = (cc_uint32*)buffer.bits + b.left;
size = (b.right - b.left) * 4;
for (y = b.top; y < b.bottom; y++) {
Mem_Copy(dst + y * buffer.stride, src + y * fb_bmp.width, size);
}
res = ANativeWindow_unlockAndPost(win_handle);
if (res) Logger_Abort2(res, "Unlocking window pixels");
}
void Window_FreeFramebuffer(struct Bitmap* bmp) {
Mem_Free(bmp->scan0);
}
void Window_OpenKeyboard(const struct OpenKeyboardArgs* kArgs) {
JNIEnv* env;
jvalue args[2];
JavaGetCurrentEnv(env);
args[0].l = JavaMakeString(env, kArgs->text);
args[1].i = kArgs->type;
JavaCallVoid(env, "openKeyboard", "(Ljava/lang/String;I)V", args);
(*env)->DeleteLocalRef(env, args[0].l);
}
void Window_SetKeyboardText(const cc_string* text) {
JNIEnv* env;
jvalue args[1];
JavaGetCurrentEnv(env);
args[0].l = JavaMakeString(env, text);
JavaCallVoid(env, "setKeyboardText", "(Ljava/lang/String;)V", args);
(*env)->DeleteLocalRef(env, args[0].l);
}
void Window_CloseKeyboard(void) {
JNIEnv* env;
JavaGetCurrentEnv(env);
JavaCallVoid(env, "closeKeyboard", "()V", NULL);
}
void Window_EnableRawMouse(void) { DefaultEnableRawMouse(); }
void Window_UpdateRawMouse(void) { }
void Window_DisableRawMouse(void) { DefaultDisableRawMouse(); }
#endif

358
src/Window_SDL.c Normal file
View File

@ -0,0 +1,358 @@
#include "Core.h"
#if defined CC_BUILD_SDL
#include "_WindowBase.h"
#include "Graphics.h"
#include "String.h"
#include "Funcs.h"
#include "Bitmap.h"
#include "Errors.h"
#include <SDL2/SDL.h>
static SDL_Window* win_handle;
static void RefreshWindowBounds(void) {
SDL_GetWindowSize(win_handle, &WindowInfo.Width, &WindowInfo.Height);
}
static void Window_SDLFail(const char* place) {
char strBuffer[256];
cc_string str;
String_InitArray_NT(str, strBuffer);
String_Format2(&str, "Error when %c: %c", place, SDL_GetError());
str.buffer[str.length] = '\0';
Logger_Abort(str.buffer);
}
void Window_Init(void) {
SDL_DisplayMode mode = { 0 };
SDL_Init(SDL_INIT_VIDEO);
SDL_GetDesktopDisplayMode(0, &mode);
DisplayInfo.Width = mode.w;
DisplayInfo.Height = mode.h;
DisplayInfo.Depth = SDL_BITSPERPIXEL(mode.format);
DisplayInfo.ScaleX = 1;
DisplayInfo.ScaleY = 1;
}
void Window_Create(int width, int height) {
int x = Display_CentreX(width);
int y = Display_CentreY(height);
/* TODO: Don't set this flag for launcher window */
win_handle = SDL_CreateWindow(NULL, x, y, width, height, SDL_WINDOW_OPENGL);
if (!win_handle) Window_SDLFail("creating window");
RefreshWindowBounds();
WindowInfo.Exists = true;
WindowInfo.Handle = win_handle;
}
void Window_SetTitle(const cc_string* title) {
char str[NATIVE_STR_LEN];
Platform_EncodeUtf8(str, title);
SDL_SetWindowTitle(win_handle, str);
}
void Clipboard_GetText(cc_string* value) {
char* ptr = SDL_GetClipboardText();
if (!ptr) return;
int len = String_Length(ptr);
String_AppendUtf8(value, ptr, len);
SDL_free(ptr);
}
void Clipboard_SetText(const cc_string* value) {
char str[NATIVE_STR_LEN];
Platform_EncodeUtf8(str, value);
SDL_SetClipboardText(str);
}
void Window_Show(void) { SDL_ShowWindow(win_handle); }
int Window_GetWindowState(void) {
Uint32 flags = SDL_GetWindowFlags(win_handle);
if (flags & SDL_WINDOW_MINIMIZED) return WINDOW_STATE_MINIMISED;
if (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) return WINDOW_STATE_FULLSCREEN;
return WINDOW_STATE_NORMAL;
}
cc_result Window_EnterFullscreen(void) {
return SDL_SetWindowFullscreen(win_handle, SDL_WINDOW_FULLSCREEN_DESKTOP);
}
cc_result Window_ExitFullscreen(void) { SDL_RestoreWindow(win_handle); return 0; }
void Window_SetSize(int width, int height) {
SDL_SetWindowSize(win_handle, width, height);
}
void Window_Close(void) {
SDL_Event e;
e.type = SDL_QUIT;
SDL_PushEvent(&e);
}
static int MapNativeKey(SDL_Keycode k) {
if (k >= SDLK_0 && k <= SDLK_9) { return '0' + (k - SDLK_0); }
if (k >= SDLK_a && k <= SDLK_z) { return 'A' + (k - SDLK_a); }
if (k >= SDLK_F1 && k <= SDLK_F12) { return KEY_F1 + (k - SDLK_F1); }
if (k >= SDLK_F13 && k <= SDLK_F24) { return KEY_F13 + (k - SDLK_F13); }
/* SDLK_KP_0 isn't before SDLK_KP_1 */
if (k >= SDLK_KP_1 && k <= SDLK_KP_9) { return KEY_KP1 + (k - SDLK_KP_1); }
switch (k) {
case SDLK_RETURN: return KEY_ENTER;
case SDLK_ESCAPE: return KEY_ESCAPE;
case SDLK_BACKSPACE: return KEY_BACKSPACE;
case SDLK_TAB: return KEY_TAB;
case SDLK_SPACE: return KEY_SPACE;
case SDLK_QUOTE: return KEY_QUOTE;
case SDLK_EQUALS: return KEY_EQUALS;
case SDLK_COMMA: return KEY_COMMA;
case SDLK_MINUS: return KEY_MINUS;
case SDLK_PERIOD: return KEY_PERIOD;
case SDLK_SLASH: return KEY_SLASH;
case SDLK_SEMICOLON: return KEY_SEMICOLON;
case SDLK_LEFTBRACKET: return KEY_LBRACKET;
case SDLK_BACKSLASH: return KEY_BACKSLASH;
case SDLK_RIGHTBRACKET: return KEY_RBRACKET;
case SDLK_BACKQUOTE: return KEY_TILDE;
case SDLK_CAPSLOCK: return KEY_CAPSLOCK;
case SDLK_PRINTSCREEN: return KEY_PRINTSCREEN;
case SDLK_SCROLLLOCK: return KEY_SCROLLLOCK;
case SDLK_PAUSE: return KEY_PAUSE;
case SDLK_INSERT: return KEY_INSERT;
case SDLK_HOME: return KEY_HOME;
case SDLK_PAGEUP: return KEY_PAGEUP;
case SDLK_DELETE: return KEY_DELETE;
case SDLK_END: return KEY_END;
case SDLK_PAGEDOWN: return KEY_PAGEDOWN;
case SDLK_RIGHT: return KEY_RIGHT;
case SDLK_LEFT: return KEY_LEFT;
case SDLK_DOWN: return KEY_DOWN;
case SDLK_UP: return KEY_UP;
case SDLK_NUMLOCKCLEAR: return KEY_NUMLOCK;
case SDLK_KP_DIVIDE: return KEY_KP_DIVIDE;
case SDLK_KP_MULTIPLY: return KEY_KP_MULTIPLY;
case SDLK_KP_MINUS: return KEY_KP_MINUS;
case SDLK_KP_PLUS: return KEY_KP_PLUS;
case SDLK_KP_ENTER: return KEY_KP_ENTER;
case SDLK_KP_0: return KEY_KP0;
case SDLK_KP_PERIOD: return KEY_KP_DECIMAL;
case SDLK_LCTRL: return KEY_LCTRL;
case SDLK_LSHIFT: return KEY_LSHIFT;
case SDLK_LALT: return KEY_LALT;
case SDLK_LGUI: return KEY_LWIN;
case SDLK_RCTRL: return KEY_RCTRL;
case SDLK_RSHIFT: return KEY_RSHIFT;
case SDLK_RALT: return KEY_RALT;
case SDLK_RGUI: return KEY_RWIN;
}
return KEY_NONE;
}
static void OnKeyEvent(const SDL_Event* e) {
cc_bool pressed = e->key.state == SDL_PRESSED;
int key = MapNativeKey(e->key.keysym.sym);
if (key) Input_Set(key, pressed);
}
static void OnMouseEvent(const SDL_Event* e) {
cc_bool pressed = e->button.state == SDL_PRESSED;
int btn;
switch (e->button.button) {
case SDL_BUTTON_LEFT: btn = KEY_LMOUSE; break;
case SDL_BUTTON_MIDDLE: btn = KEY_MMOUSE; break;
case SDL_BUTTON_RIGHT: btn = KEY_RMOUSE; break;
case SDL_BUTTON_X1: btn = KEY_XBUTTON1; break;
case SDL_BUTTON_X2: btn = KEY_XBUTTON2; break;
default: return;
}
Input_Set(btn, pressed);
}
static void OnTextEvent(const SDL_Event* e) {
char buffer[SDL_TEXTINPUTEVENT_TEXT_SIZE];
cc_string str;
int i, len;
String_InitArray(str, buffer);
len = String_CalcLen(e->text.text, SDL_TEXTINPUTEVENT_TEXT_SIZE);
String_AppendUtf8(&str, e->text.text, len);
for (i = 0; i < str.length; i++) {
Event_RaiseInt(&InputEvents.Press, str.buffer[i]);
}
}
static void OnWindowEvent(const SDL_Event* e) {
switch (e->window.event) {
case SDL_WINDOWEVENT_EXPOSED:
Event_RaiseVoid(&WindowEvents.Redraw);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
RefreshWindowBounds();
Event_RaiseVoid(&WindowEvents.Resized);
break;
case SDL_WINDOWEVENT_MINIMIZED:
case SDL_WINDOWEVENT_MAXIMIZED:
case SDL_WINDOWEVENT_RESTORED:
Event_RaiseVoid(&WindowEvents.StateChanged);
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
WindowInfo.Focused = true;
Event_RaiseVoid(&WindowEvents.FocusChanged);
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
WindowInfo.Focused = false;
Event_RaiseVoid(&WindowEvents.FocusChanged);
break;
case SDL_WINDOWEVENT_CLOSE:
Window_Close();
break;
}
}
void Window_ProcessEvents(void) {
SDL_Event e;
while (SDL_PollEvent(&e)) {
switch (e.type) {
case SDL_KEYDOWN:
case SDL_KEYUP:
OnKeyEvent(&e); break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
OnMouseEvent(&e); break;
case SDL_MOUSEWHEEL:
Mouse_ScrollWheel(e.wheel.y);
break;
case SDL_MOUSEMOTION:
Pointer_SetPosition(0, e.motion.x, e.motion.y);
if (Input_RawMode) Event_RaiseRawMove(&PointerEvents.RawMoved, e.motion.xrel, e.motion.yrel);
break;
case SDL_TEXTINPUT:
OnTextEvent(&e); break;
case SDL_WINDOWEVENT:
OnWindowEvent(&e); break;
case SDL_QUIT:
WindowInfo.Exists = false;
Event_RaiseVoid(&WindowEvents.Closing);
SDL_DestroyWindow(win_handle);
break;
case SDL_RENDER_DEVICE_RESET:
Gfx_LoseContext("SDL device reset event");
Gfx_RecreateContext();
break;
}
}
}
static void Cursor_GetRawPos(int* x, int* y) {
SDL_GetMouseState(x, y);
}
void Cursor_SetPosition(int x, int y) {
SDL_WarpMouseInWindow(win_handle, x, y);
}
static void Cursor_DoSetVisible(cc_bool visible) {
SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE);
}
static void ShowDialogCore(const char* title, const char* msg) {
SDL_ShowSimpleMessageBox(0, title, msg, win_handle);
}
static SDL_Surface* surface;
void Window_AllocFramebuffer(struct Bitmap* bmp) {
surface = SDL_GetWindowSurface(win_handle);
if (!surface) Window_SDLFail("getting window surface");
if (SDL_MUSTLOCK(surface)) {
int ret = SDL_LockSurface(surface);
if (ret < 0) Window_SDLFail("locking window surface");
}
bmp->scan0 = surface->pixels;
}
void Window_DrawFramebuffer(Rect2D r) {
SDL_Rect rect;
rect.x = r.X; rect.w = r.Width;
rect.y = r.Y; rect.h = r.Height;
SDL_UpdateWindowSurfaceRects(win_handle, &rect, 1);
}
void Window_FreeFramebuffer(struct Bitmap* bmp) {
/* SDL docs explicitly say to NOT free the surface */
/* https://wiki.libsdl.org/SDL_GetWindowSurface */
/* TODO: Do we still need to unlock it though? */
}
void Window_OpenKeyboard(const struct OpenKeyboardArgs* args) { SDL_StartTextInput(); }
void Window_SetKeyboardText(const cc_string* text) { }
void Window_CloseKeyboard(void) { SDL_StopTextInput(); }
void Window_EnableRawMouse(void) {
RegrabMouse();
SDL_SetRelativeMouseMode(true);
Input_RawMode = true;
}
void Window_UpdateRawMouse(void) { CentreMousePosition(); }
void Window_DisableRawMouse(void) {
RegrabMouse();
SDL_SetRelativeMouseMode(false);
Input_RawMode = false;
}
/*########################################################################################################################*
*-----------------------------------------------------OpenGL context------------------------------------------------------*
*#########################################################################################################################*/
#if defined CC_BUILD_GL && !defined CC_BUILD_EGL
static SDL_GLContext win_ctx;
void GLContext_Create(void) {
struct GraphicsMode mode;
InitGraphicsMode(&mode);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, mode.R);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, mode.G);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, mode.B);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, mode.A);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, GLCONTEXT_DEFAULT_DEPTH);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, true);
win_ctx = SDL_GL_CreateContext(win_handle);
if (!win_ctx) Window_SDLFail("creating OpenGL context");
}
void GLContext_Update(void) { }
cc_bool GLContext_TryRestore(void) { return true; }
void GLContext_Free(void) {
SDL_GL_DeleteContext(win_ctx);
win_ctx = NULL;
}
void* GLContext_GetAddress(const char* function) {
return SDL_GL_GetProcAddress(function);
}
cc_bool GLContext_SwapBuffers(void) {
SDL_GL_SwapWindow(win_handle);
return true;
}
void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
SDL_GL_SetSwapInterval(vsync);
}
void GLContext_GetApiInfo(cc_string* info) { }
#endif
#endif

651
src/Window_Web.c Normal file
View File

@ -0,0 +1,651 @@
#include "Core.h"
#if defined CC_BUILD_WEB && !defined CC_BUILD_SDL
#include "_WindowBase.h"
#include "Game.h"
#include "String.h"
#include "Funcs.h"
#include "ExtMath.h"
#include "Bitmap.h"
#include "Errors.h"
#include <emscripten/emscripten.h>
#include <emscripten/html5.h>
#include <emscripten/key_codes.h>
extern int interop_CanvasWidth(void);
extern int interop_CanvasHeight(void);
extern int interop_ScreenWidth(void);
extern int interop_ScreenHeight(void);
static cc_bool keyboardOpen, needResize;
static int RawDpiScale(int x) { return (int)(x * emscripten_get_device_pixel_ratio()); }
static int GetScreenWidth(void) { return RawDpiScale(interop_ScreenWidth()); }
static int GetScreenHeight(void) { return RawDpiScale(interop_ScreenHeight()); }
static void UpdateWindowBounds(void) {
int width = interop_CanvasWidth();
int height = interop_CanvasHeight();
if (width == WindowInfo.Width && height == WindowInfo.Height) return;
WindowInfo.Width = width;
WindowInfo.Height = height;
Event_RaiseVoid(&WindowEvents.Resized);
}
static void SetFullscreenBounds(void) {
int width = GetScreenWidth();
int height = GetScreenHeight();
emscripten_set_canvas_element_size("#canvas", width, height);
}
/* Browser only allows pointer lock requests in response to user input */
static void DeferredEnableRawMouse(void) {
EmscriptenPointerlockChangeEvent status;
if (!Input_RawMode) return;
status.isActive = false;
emscripten_get_pointerlock_status(&status);
if (!status.isActive) emscripten_request_pointerlock("#canvas", false);
}
static EM_BOOL OnMouseWheel(int type, const EmscriptenWheelEvent* ev, void* data) {
/* TODO: The scale factor isn't standardised.. is there a better way though? */
Mouse_ScrollWheel(-Math_Sign(ev->deltaY));
DeferredEnableRawMouse();
return true;
}
static EM_BOOL OnMouseButton(int type, const EmscriptenMouseEvent* ev, void* data) {
cc_bool down = type == EMSCRIPTEN_EVENT_MOUSEDOWN;
/* https://stackoverflow.com/questions/60895686/how-to-get-mouse-buttons-4-5-browser-back-browser-forward-working-in-firef */
switch (ev->button) {
case 0: Input_Set(KEY_LMOUSE, down); break;
case 1: Input_Set(KEY_MMOUSE, down); break;
case 2: Input_Set(KEY_RMOUSE, down); break;
case 3: Input_Set(KEY_XBUTTON1, down); break;
case 4: Input_Set(KEY_XBUTTON2, down); break;
}
DeferredEnableRawMouse();
return true;
}
/* input coordinates are CSS pixels, remap to internal pixels */
static void RescaleXY(int* x, int* y) {
double css_width, css_height;
emscripten_get_element_css_size("#canvas", &css_width, &css_height);
if (css_width && css_height) {
*x = (int)(*x * WindowInfo.Width / css_width );
*y = (int)(*y * WindowInfo.Height / css_height);
} else {
/* If css width or height is 0, something is bogus */
/* Better to avoid divsision by 0 in that case though */
}
}
static EM_BOOL OnMouseMove(int type, const EmscriptenMouseEvent* ev, void* data) {
int x, y, buttons = ev->buttons;
/* Set before position change, in case mouse buttons changed when outside window */
Input_SetNonRepeatable(KEY_LMOUSE, buttons & 0x01);
Input_SetNonRepeatable(KEY_RMOUSE, buttons & 0x02);
Input_SetNonRepeatable(KEY_MMOUSE, buttons & 0x04);
x = ev->targetX; y = ev->targetY;
RescaleXY(&x, &y);
Pointer_SetPosition(0, x, y);
if (Input_RawMode) Event_RaiseRawMove(&PointerEvents.RawMoved, ev->movementX, ev->movementY);
return true;
}
/* TODO: Also query mouse coordinates globally (in OnMouseMove) and reuse interop_AdjustXY here */
extern void interop_AdjustXY(int* x, int* y);
static EM_BOOL OnTouchStart(int type, const EmscriptenTouchEvent* ev, void* data) {
const EmscriptenTouchPoint* t;
int i, x, y;
for (i = 0; i < ev->numTouches; ++i) {
t = &ev->touches[i];
if (!t->isChanged) continue;
x = t->targetX; y = t->targetY;
interop_AdjustXY(&x, &y);
RescaleXY(&x, &y);
Input_AddTouch(t->identifier, x, y);
}
/* Don't intercept touchstart events while keyboard is open, that way */
/* user can still touch to move the caret position in input textbox. */
return !keyboardOpen;
}
static EM_BOOL OnTouchMove(int type, const EmscriptenTouchEvent* ev, void* data) {
const EmscriptenTouchPoint* t;
int i, x, y;
for (i = 0; i < ev->numTouches; ++i) {
t = &ev->touches[i];
if (!t->isChanged) continue;
x = t->targetX; y = t->targetY;
interop_AdjustXY(&x, &y);
RescaleXY(&x, &y);
Input_UpdateTouch(t->identifier, x, y);
}
/* Don't intercept touchmove events while keyboard is open, that way */
/* user can still touch to move the caret position in input textbox. */
return !keyboardOpen;
}
static EM_BOOL OnTouchEnd(int type, const EmscriptenTouchEvent* ev, void* data) {
const EmscriptenTouchPoint* t;
int i, x, y;
for (i = 0; i < ev->numTouches; ++i) {
t = &ev->touches[i];
if (!t->isChanged) continue;
x = t->targetX; y = t->targetY;
interop_AdjustXY(&x, &y);
RescaleXY(&x, &y);
Input_RemoveTouch(t->identifier, x, y);
}
/* Don't intercept touchend events while keyboard is open, that way */
/* user can still touch to move the caret position in input textbox. */
return !keyboardOpen;
}
static EM_BOOL OnFocus(int type, const EmscriptenFocusEvent* ev, void* data) {
WindowInfo.Focused = type == EMSCRIPTEN_EVENT_FOCUS;
Event_RaiseVoid(&WindowEvents.FocusChanged);
return true;
}
static EM_BOOL OnResize(int type, const EmscriptenUiEvent* ev, void *data) {
UpdateWindowBounds(); needResize = true;
return true;
}
/* This is only raised when going into fullscreen */
static EM_BOOL OnCanvasResize(int type, const void* reserved, void *data) {
UpdateWindowBounds(); needResize = true;
return false;
}
static EM_BOOL OnFullscreenChange(int type, const EmscriptenFullscreenChangeEvent* ev, void *data) {
UpdateWindowBounds(); needResize = true;
return false;
}
static const char* OnBeforeUnload(int type, const void* ev, void *data) {
if (!Game_ShouldClose()) {
/* Exit pointer lock, otherwise when you press Ctrl+W, the */
/* cursor remains invisible in the confirmation dialog */
emscripten_exit_pointerlock();
return "You have unsaved changes. Are you sure you want to quit?";
}
Window_Close();
return NULL;
}
static int MapNativeKey(int k, int l) {
if (k >= '0' && k <= '9') return k;
if (k >= 'A' && k <= 'Z') return k;
if (k >= DOM_VK_F1 && k <= DOM_VK_F24) { return KEY_F1 + (k - DOM_VK_F1); }
if (k >= DOM_VK_NUMPAD0 && k <= DOM_VK_NUMPAD9) { return KEY_KP0 + (k - DOM_VK_NUMPAD0); }
switch (k) {
case DOM_VK_BACK_SPACE: return KEY_BACKSPACE;
case DOM_VK_TAB: return KEY_TAB;
case DOM_VK_RETURN: return l == DOM_KEY_LOCATION_NUMPAD ? KEY_KP_ENTER : KEY_ENTER;
case DOM_VK_SHIFT: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RSHIFT : KEY_LSHIFT;
case DOM_VK_CONTROL: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RCTRL : KEY_LCTRL;
case DOM_VK_ALT: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RALT : KEY_LALT;
case DOM_VK_PAUSE: return KEY_PAUSE;
case DOM_VK_CAPS_LOCK: return KEY_CAPSLOCK;
case DOM_VK_ESCAPE: return KEY_ESCAPE;
case DOM_VK_SPACE: return KEY_SPACE;
case DOM_VK_PAGE_UP: return KEY_PAGEUP;
case DOM_VK_PAGE_DOWN: return KEY_PAGEDOWN;
case DOM_VK_END: return KEY_END;
case DOM_VK_HOME: return KEY_HOME;
case DOM_VK_LEFT: return KEY_LEFT;
case DOM_VK_UP: return KEY_UP;
case DOM_VK_RIGHT: return KEY_RIGHT;
case DOM_VK_DOWN: return KEY_DOWN;
case DOM_VK_PRINTSCREEN: return KEY_PRINTSCREEN;
case DOM_VK_INSERT: return KEY_INSERT;
case DOM_VK_DELETE: return KEY_DELETE;
case DOM_VK_SEMICOLON: return KEY_SEMICOLON;
case DOM_VK_EQUALS: return KEY_EQUALS;
case DOM_VK_WIN: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RWIN : KEY_LWIN;
case DOM_VK_MULTIPLY: return KEY_KP_MULTIPLY;
case DOM_VK_ADD: return KEY_KP_PLUS;
case DOM_VK_SUBTRACT: return KEY_KP_MINUS;
case DOM_VK_DECIMAL: return KEY_KP_DECIMAL;
case DOM_VK_DIVIDE: return KEY_KP_DIVIDE;
case DOM_VK_NUM_LOCK: return KEY_NUMLOCK;
case DOM_VK_SCROLL_LOCK: return KEY_SCROLLLOCK;
case DOM_VK_HYPHEN_MINUS: return KEY_MINUS;
case DOM_VK_COMMA: return KEY_COMMA;
case DOM_VK_PERIOD: return KEY_PERIOD;
case DOM_VK_SLASH: return KEY_SLASH;
case DOM_VK_BACK_QUOTE: return KEY_TILDE;
case DOM_VK_OPEN_BRACKET: return KEY_LBRACKET;
case DOM_VK_BACK_SLASH: return KEY_BACKSLASH;
case DOM_VK_CLOSE_BRACKET: return KEY_RBRACKET;
case DOM_VK_QUOTE: return KEY_QUOTE;
/* chrome */
case 186: return KEY_SEMICOLON;
case 187: return KEY_EQUALS;
case 189: return KEY_MINUS;
}
return KEY_NONE;
}
static EM_BOOL OnKeyDown(int type, const EmscriptenKeyboardEvent* ev, void* data) {
int key = MapNativeKey(ev->keyCode, ev->location);
/* iOS safari still sends backspace key events, don't intercept those */
if (key == KEY_BACKSPACE && Input_TouchMode && keyboardOpen) return false;
if (key) Input_SetPressed(key);
DeferredEnableRawMouse();
if (!key) return false;
/* If holding down Ctrl or Alt, keys aren't going to generate a KeyPress event anyways. */
/* This intercepts Ctrl+S etc. Ctrl+C and Ctrl+V are not intercepted for clipboard. */
/* NOTE: macOS uses Win (Command) key instead of Ctrl, have to account for that too */
if (Key_IsAltPressed()) return true;
if (Key_IsWinPressed()) return key != 'C' && key != 'V';
if (Key_IsCtrlPressed()) return key != 'C' && key != 'V';
/* Space needs special handling, as intercepting this prevents the ' ' key press event */
/* But on Safari, space scrolls the page - so need to intercept when keyboard is NOT open */
if (key == KEY_SPACE) return !keyboardOpen;
/* Must not intercept KeyDown for regular keys, otherwise KeyPress doesn't get raised. */
/* However, do want to prevent browser's behaviour on F11, F5, home etc. */
/* e.g. not preventing F11 means browser makes page fullscreen instead of just canvas */
return (key >= KEY_F1 && key <= KEY_F24) || (key >= KEY_UP && key <= KEY_RIGHT) ||
(key >= KEY_INSERT && key <= KEY_MENU) || (key >= KEY_ENTER && key <= KEY_NUMLOCK);
}
static EM_BOOL OnKeyUp(int type, const EmscriptenKeyboardEvent* ev, void* data) {
int key = MapNativeKey(ev->keyCode, ev->location);
if (key) Input_SetReleased(key);
DeferredEnableRawMouse();
return key != KEY_NONE;
}
static EM_BOOL OnKeyPress(int type, const EmscriptenKeyboardEvent* ev, void* data) {
char keyChar;
DeferredEnableRawMouse();
/* When on-screen keyboard is open, we don't want to intercept any key presses, */
/* because they should be sent to the HTML text input instead. */
/* (Chrome for android sends keypresses sometimes for '0' to '9' keys) */
/* - If any keys are intercepted, this causes the HTML text input to become */
/* desynchronised from the chat/menu input widget the user sees in game. */
/* - This causes problems such as attempting to backspace all text later to */
/* not actually backspace everything. (because the HTML text input does not */
/* have these intercepted key presses in its text buffer) */
if (Input_TouchMode && keyboardOpen) return false;
/* Safari on macOS still sends a keypress event, which must not be cancelled */
/* (otherwise copy/paste doesn't work, as it uses Win+C / Win+V) */
if (ev->metaKey) return false;
if (Convert_TryCodepointToCP437(ev->charCode, &keyChar)) {
Event_RaiseInt(&InputEvents.Press, keyChar);
}
return true;
}
/* Really old emscripten versions (e.g. 1.38.21) don't have this defined */
/* Can't just use "#window", newer versions switched to const int instead */
#ifndef EMSCRIPTEN_EVENT_TARGET_WINDOW
#define EMSCRIPTEN_EVENT_TARGET_WINDOW "#window"
#endif
static void HookEvents(void) {
emscripten_set_wheel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnMouseWheel);
emscripten_set_mousedown_callback("#canvas", NULL, 0, OnMouseButton);
emscripten_set_mouseup_callback("#canvas", NULL, 0, OnMouseButton);
emscripten_set_mousemove_callback("#canvas", NULL, 0, OnMouseMove);
emscripten_set_fullscreenchange_callback("#canvas", NULL, 0, OnFullscreenChange);
emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnFocus);
emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnFocus);
emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnResize);
emscripten_set_beforeunload_callback( NULL, OnBeforeUnload);
emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKeyDown);
emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKeyUp);
emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKeyPress);
emscripten_set_touchstart_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnTouchStart);
emscripten_set_touchmove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnTouchMove);
emscripten_set_touchend_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnTouchEnd);
emscripten_set_touchcancel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnTouchEnd);
}
static void UnhookEvents(void) {
emscripten_set_wheel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
emscripten_set_mousedown_callback("#canvas", NULL, 0, NULL);
emscripten_set_mouseup_callback("#canvas", NULL, 0, NULL);
emscripten_set_mousemove_callback("#canvas", NULL, 0, NULL);
emscripten_set_focus_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
emscripten_set_blur_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
emscripten_set_beforeunload_callback( NULL, NULL);
emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
emscripten_set_touchstart_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
emscripten_set_touchmove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
emscripten_set_touchend_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
emscripten_set_touchcancel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, NULL);
}
extern int interop_IsAndroid(void);
extern int interop_IsIOS(void);
extern void interop_AddClipboardListeners(void);
extern void interop_ForceTouchPageLayout(void);
void Window_Init(void) {
int is_ios, droid;
DisplayInfo.Width = GetScreenWidth();
DisplayInfo.Height = GetScreenHeight();
DisplayInfo.Depth = 24;
DisplayInfo.ScaleX = emscripten_get_device_pixel_ratio();
DisplayInfo.ScaleY = DisplayInfo.ScaleX;
interop_AddClipboardListeners();
droid = interop_IsAndroid();
is_ios = interop_IsIOS();
Input_SetTouchMode(is_ios || droid);
/* iOS shifts the whole webpage up when opening chat, which causes problems */
/* as the chat/send butons are positioned at the top of the canvas - they */
/* get pushed offscreen and can't be used at all anymore. So handle this */
/* case specially by positioning them at the bottom instead for iOS. */
WindowInfo.SoftKeyboard = is_ios ? SOFT_KEYBOARD_SHIFT : SOFT_KEYBOARD_RESIZE;
/* Let the webpage know it needs to force a mobile layout */
if (!Input_TouchMode) return;
interop_ForceTouchPageLayout();
}
extern void interop_InitContainer(void);
void Window_Create(int width, int height) {
WindowInfo.Exists = true;
WindowInfo.Focused = true;
HookEvents();
/* Let the webpage decide on initial bounds */
WindowInfo.Width = interop_CanvasWidth();
WindowInfo.Height = interop_CanvasHeight();
interop_InitContainer();
}
extern void interop_SetPageTitle(const char* title);
void Window_SetTitle(const cc_string* title) {
char str[NATIVE_STR_LEN];
Platform_EncodeUtf8(str, title);
interop_SetPageTitle(str);
}
static char pasteBuffer[512];
static cc_string pasteStr;
EMSCRIPTEN_KEEPALIVE void Window_RequestClipboardText(void) {
Event_RaiseInput(&InputEvents.Down, INPUT_CLIPBOARD_COPY, 0);
}
EMSCRIPTEN_KEEPALIVE void Window_StoreClipboardText(char* src) {
String_InitArray(pasteStr, pasteBuffer);
String_AppendUtf8(&pasteStr, src, String_CalcLen(src, 2048));
}
EMSCRIPTEN_KEEPALIVE void Window_GotClipboardText(char* src) {
Window_StoreClipboardText(src);
Event_RaiseInput(&InputEvents.Down, INPUT_CLIPBOARD_PASTE, 0);
}
extern void interop_TryGetClipboardText(void);
void Clipboard_GetText(cc_string* value) {
/* Window_StoreClipboardText may or may not be called by this */
interop_TryGetClipboardText();
String_Copy(value, &pasteStr);
pasteStr.length = 0;
}
extern void interop_TrySetClipboardText(const char* text);
void Clipboard_SetText(const cc_string* value) {
char str[NATIVE_STR_LEN];
Platform_EncodeUtf8(str, value);
interop_TrySetClipboardText(str);
}
void Window_Show(void) { }
int Window_GetWindowState(void) {
EmscriptenFullscreenChangeEvent status = { 0 };
emscripten_get_fullscreen_status(&status);
return status.isFullscreen ? WINDOW_STATE_FULLSCREEN : WINDOW_STATE_NORMAL;
}
extern int interop_GetContainerID(void);
extern void interop_EnterFullscreen(void);
cc_result Window_EnterFullscreen(void) {
EmscriptenFullscreenStrategy strategy;
const char* target;
int res;
strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH;
strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF;
strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT;
strategy.canvasResizedCallback = OnCanvasResize;
strategy.canvasResizedCallbackUserData = NULL;
/* TODO: Return container element ID instead of hardcoding here */
res = interop_GetContainerID();
target = res ? "canvas_wrapper" : "#canvas";
res = emscripten_request_fullscreen_strategy(target, 1, &strategy);
if (res == EMSCRIPTEN_RESULT_NOT_SUPPORTED) res = ERR_NOT_SUPPORTED;
if (res) return res;
interop_EnterFullscreen();
return 0;
}
cc_result Window_ExitFullscreen(void) {
emscripten_exit_fullscreen();
UpdateWindowBounds();
return 0;
}
void Window_SetSize(int width, int height) {
emscripten_set_canvas_element_size("#canvas", width, height);
/* CSS size is in CSS units not pixel units */
emscripten_set_element_css_size("#canvas", width / DisplayInfo.ScaleX, height / DisplayInfo.ScaleY);
UpdateWindowBounds();
}
void Window_Close(void) {
WindowInfo.Exists = false;
Event_RaiseVoid(&WindowEvents.Closing);
/* If the game is closed while in fullscreen, the last rendered frame stays */
/* shown in fullscreen, but the game can't be interacted with anymore */
Window_ExitFullscreen();
/* Don't want cursor stuck on the dead 0,0 canvas */
Window_DisableRawMouse();
Window_SetSize(0, 0);
UnhookEvents();
}
extern void interop_RequestCanvasResize(void);
void Window_ProcessEvents(void) {
if (!needResize) return;
needResize = false;
if (!WindowInfo.Exists) return;
if (Window_GetWindowState() == WINDOW_STATE_FULLSCREEN) {
SetFullscreenBounds();
} else {
/* Webpage can adjust canvas size if it wants to */
interop_RequestCanvasResize();
}
UpdateWindowBounds();
}
/* Not needed because browser provides relative mouse and touch events */
static void Cursor_GetRawPos(int* x, int* y) { *x = 0; *y = 0; }
/* Not allowed to move cursor from javascript */
void Cursor_SetPosition(int x, int y) { }
extern void interop_SetCursorVisible(int visible);
static void Cursor_DoSetVisible(cc_bool visible) {
interop_SetCursorVisible(visible);
}
extern void interop_ShowDialog(const char* title, const char* msg);
static void ShowDialogCore(const char* title, const char* msg) {
interop_ShowDialog(title, msg);
}
static OpenFileDialogCallback uploadCallback;
EMSCRIPTEN_KEEPALIVE void Window_OnFileUploaded(const char* src) {
cc_string file; char buffer[FILENAME_SIZE];
String_InitArray(file, buffer);
String_AppendUtf8(&file, src, String_Length(src));
uploadCallback(&file);
uploadCallback = NULL;
}
extern void interop_OpenFileDialog(const char* filter);
cc_result Window_OpenFileDialog(const char* filter, OpenFileDialogCallback callback) {
uploadCallback = callback;
/* Calls Window_OnFileUploaded on success */
interop_OpenFileDialog(filter);
return ERR_NOT_SUPPORTED;
}
void Window_AllocFramebuffer(struct Bitmap* bmp) { }
void Window_DrawFramebuffer(Rect2D r) { }
void Window_FreeFramebuffer(struct Bitmap* bmp) { }
extern void interop_OpenKeyboard(const char* text, int type, const char* placeholder);
extern void interop_SetKeyboardText(const char* text);
extern void interop_CloseKeyboard(void);
EMSCRIPTEN_KEEPALIVE void Window_OnTextChanged(const char* src) {
cc_string str; char buffer[800];
String_InitArray(str, buffer);
String_AppendUtf8(&str, src, String_CalcLen(src, 3200));
Event_RaiseString(&InputEvents.TextChanged, &str);
}
void Window_OpenKeyboard(const struct OpenKeyboardArgs* args) {
char str[NATIVE_STR_LEN];
keyboardOpen = true;
if (!Input_TouchMode) return;
Platform_EncodeUtf8(str, args->text);
Platform_LogConst("OPEN SESAME");
interop_OpenKeyboard(str, args->type, args->placeholder);
}
void Window_SetKeyboardText(const cc_string* text) {
char str[NATIVE_STR_LEN];
if (!Input_TouchMode) return;
Platform_EncodeUtf8(str, text);
interop_SetKeyboardText(str);
}
void Window_CloseKeyboard(void) {
keyboardOpen = false;
if (!Input_TouchMode) return;
interop_CloseKeyboard();
}
void Window_EnableRawMouse(void) {
RegrabMouse();
/* defer pointerlock request until next user input */
Input_RawMode = true;
}
void Window_UpdateRawMouse(void) { }
void Window_DisableRawMouse(void) {
RegrabMouse();
emscripten_exit_pointerlock();
Input_RawMode = false;
}
/*########################################################################################################################*
*------------------------------------------------Emscripten WebGL context-------------------------------------------------*
*#########################################################################################################################*/
#ifdef CC_BUILD_GL
#include "Graphics.h"
static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx_handle;
static EM_BOOL GLContext_OnLost(int eventType, const void *reserved, void *userData) {
Gfx_LoseContext("WebGL context lost");
return 1;
}
void GLContext_Create(void) {
EmscriptenWebGLContextAttributes attribs;
emscripten_webgl_init_context_attributes(&attribs);
attribs.alpha = false;
attribs.depth = true;
attribs.stencil = false;
attribs.antialias = false;
ctx_handle = emscripten_webgl_create_context("#canvas", &attribs);
if (!ctx_handle) Window_ShowDialog("WebGL unsupported", "WebGL is required to run ClassiCube");
emscripten_webgl_make_context_current(ctx_handle);
emscripten_set_webglcontextlost_callback("#canvas", NULL, 0, GLContext_OnLost);
}
void GLContext_Update(void) {
/* TODO: do we need to do something here.... ? */
}
cc_bool GLContext_TryRestore(void) {
return !emscripten_is_webgl_context_lost(0);
}
void GLContext_Free(void) {
emscripten_webgl_destroy_context(ctx_handle);
emscripten_set_webglcontextlost_callback("#canvas", NULL, 0, NULL);
}
void* GLContext_GetAddress(const char* function) { return NULL; }
cc_bool GLContext_SwapBuffers(void) { return true; /* Browser implicitly does this */ }
void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
if (vsync) {
emscripten_set_main_loop_timing(EM_TIMING_RAF, 1);
} else {
emscripten_set_main_loop_timing(EM_TIMING_SETTIMEOUT, (int)minFrameMs);
}
}
extern void interop_GetGpuRenderer(char* buffer, int len);
void GLContext_GetApiInfo(cc_string* info) {
char buffer[NATIVE_STR_LEN];
int len;
interop_GetGpuRenderer(buffer, NATIVE_STR_LEN);
len = String_CalcLen(buffer, NATIVE_STR_LEN);
if (!len) return;
String_AppendConst(info, "GPU: ");
String_AppendUtf8(info, buffer, len);
}
#endif
#endif

667
src/Window_Win.c Normal file
View File

@ -0,0 +1,667 @@
#include "Core.h"
#if defined CC_BUILD_WINGUI && !defined CC_BUILD_SDL
#include "_WindowBase.h"
#include "String.h"
#include "Funcs.h"
#include "Bitmap.h"
#include "Options.h"
#include "Errors.h"
#define WIN32_LEAN_AND_MEAN
#define NOSERVICE
#define NOMCX
#define NOIME
#ifndef UNICODE
#define UNICODE
#define _UNICODE
#endif
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501 /* Windows XP */
/* NOTE: Functions that are not present on Windows 2000 are dynamically loaded. */
/* Hence the actual minimum supported OS is Windows 2000. This just avoids redeclaring structs. */
#endif
#include <windows.h>
#define CC_WIN_STYLE WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN
#define CC_WIN_CLASSNAME TEXT("ClassiCube_Window")
#define Rect_Width(rect) (rect.right - rect.left)
#define Rect_Height(rect) (rect.bottom - rect.top)
#ifndef WM_XBUTTONDOWN
/* Missing if _WIN32_WINNT isn't defined */
#define WM_XBUTTONDOWN 0x020B
#define WM_XBUTTONUP 0x020C
#endif
typedef BOOL (WINAPI *FUNC_RegisterRawInput)(PCRAWINPUTDEVICE devices, UINT numDevices, UINT size);
static FUNC_RegisterRawInput _registerRawInput;
typedef UINT (WINAPI *FUNC_GetRawInputData)(HRAWINPUT hRawInput, UINT cmd, void* data, UINT* size, UINT headerSize);
static FUNC_GetRawInputData _getRawInputData;
static HINSTANCE win_instance;
static HWND win_handle;
static HDC win_DC;
static cc_bool suppress_resize;
static int win_totalWidth, win_totalHeight; /* Size of window including titlebar and borders */
static int windowX, windowY;
static cc_bool is_ansiWindow;
static const cc_uint8 key_map[14 * 16] = {
0, 0, 0, 0, 0, 0, 0, 0, KEY_BACKSPACE, KEY_TAB, 0, 0, 0, KEY_ENTER, 0, 0,
0, 0, 0, KEY_PAUSE, KEY_CAPSLOCK, 0, 0, 0, 0, 0, 0, KEY_ESCAPE, 0, 0, 0, 0,
KEY_SPACE, KEY_PAGEUP, KEY_PAGEDOWN, KEY_END, KEY_HOME, KEY_LEFT, KEY_UP, KEY_RIGHT, KEY_DOWN, 0, KEY_PRINTSCREEN, 0, KEY_PRINTSCREEN, KEY_INSERT, KEY_DELETE, 0,
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 0, 0, 0, 0, 0, 0,
0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', KEY_LWIN, KEY_RWIN, KEY_MENU, 0, 0,
KEY_KP0, KEY_KP1, KEY_KP2, KEY_KP3, KEY_KP4, KEY_KP5, KEY_KP6, KEY_KP7, KEY_KP8, KEY_KP9, KEY_KP_MULTIPLY, KEY_KP_PLUS, 0, KEY_KP_MINUS, KEY_KP_DECIMAL, KEY_KP_DIVIDE,
KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_F13, KEY_F14, KEY_F15, KEY_F16,
KEY_F17, KEY_F18, KEY_F19, KEY_F20, KEY_F21, KEY_F22, KEY_F23, KEY_F24, 0, 0, 0, 0, 0, 0, 0, 0,
KEY_NUMLOCK, KEY_SCROLLLOCK, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
KEY_LSHIFT, KEY_RSHIFT, KEY_LCTRL, KEY_RCTRL, KEY_LALT, KEY_RALT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_SEMICOLON, KEY_EQUALS, KEY_COMMA, KEY_MINUS, KEY_PERIOD, KEY_SLASH,
KEY_TILDE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_LBRACKET, KEY_BACKSLASH, KEY_RBRACKET, KEY_QUOTE, 0,
};
static int MapNativeKey(WPARAM key, LPARAM meta) {
LPARAM ext = meta & (1UL << 24);
switch (key)
{
case VK_CONTROL:
return ext ? KEY_RCTRL : KEY_LCTRL;
case VK_MENU:
return ext ? KEY_RALT : KEY_LALT;
case VK_RETURN:
return ext ? KEY_KP_ENTER : KEY_ENTER;
default:
return key < Array_Elems(key_map) ? key_map[key] : 0;
}
}
static void RefreshWindowBounds(void) {
RECT rect;
POINT topLeft = { 0, 0 };
GetWindowRect(win_handle, &rect);
win_totalWidth = Rect_Width(rect);
win_totalHeight = Rect_Height(rect);
GetClientRect(win_handle, &rect);
WindowInfo.Width = Rect_Width(rect);
WindowInfo.Height = Rect_Height(rect);
/* GetClientRect always returns 0,0 for left,top (see MSDN) */
ClientToScreen(win_handle, &topLeft);
windowX = topLeft.x; windowY = topLeft.y;
}
static LRESULT CALLBACK Window_Procedure(HWND handle, UINT message, WPARAM wParam, LPARAM lParam) {
char keyChar;
float wheelDelta;
switch (message) {
case WM_ACTIVATE:
WindowInfo.Focused = LOWORD(wParam) != 0;
Event_RaiseVoid(&WindowEvents.FocusChanged);
break;
case WM_ERASEBKGND:
return 1; /* Avoid flickering */
case WM_PAINT:
ValidateRect(win_handle, NULL);
Event_RaiseVoid(&WindowEvents.Redraw);
return 0;
case WM_WINDOWPOSCHANGED:
{
WINDOWPOS* pos = (WINDOWPOS*)lParam;
if (pos->hwnd != win_handle) break;
cc_bool sized = pos->cx != win_totalWidth || pos->cy != win_totalHeight;
RefreshWindowBounds();
if (sized && !suppress_resize) Event_RaiseVoid(&WindowEvents.Resized);
} break;
case WM_SIZE:
Event_RaiseVoid(&WindowEvents.StateChanged);
break;
case WM_CHAR:
/* TODO: Use WM_UNICHAR instead, as WM_CHAR is just utf16 */
if (Convert_TryCodepointToCP437((cc_unichar)wParam, &keyChar)) {
Event_RaiseInt(&InputEvents.Press, keyChar);
}
break;
case WM_MOUSEMOVE:
/* Set before position change, in case mouse buttons changed when outside window */
Input_SetNonRepeatable(KEY_LMOUSE, wParam & 0x01);
Input_SetNonRepeatable(KEY_RMOUSE, wParam & 0x02);
Input_SetNonRepeatable(KEY_MMOUSE, wParam & 0x10);
/* TODO: do we need to set XBUTTON1/XBUTTON2 here */
Pointer_SetPosition(0, LOWORD(lParam), HIWORD(lParam));
break;
case WM_MOUSEWHEEL:
wheelDelta = ((short)HIWORD(wParam)) / (float)WHEEL_DELTA;
Mouse_ScrollWheel(wheelDelta);
return 0;
case WM_LBUTTONDOWN:
Input_SetPressed(KEY_LMOUSE); break;
case WM_MBUTTONDOWN:
Input_SetPressed(KEY_MMOUSE); break;
case WM_RBUTTONDOWN:
Input_SetPressed(KEY_RMOUSE); break;
case WM_XBUTTONDOWN:
Input_SetPressed(HIWORD(wParam) == 1 ? KEY_XBUTTON1 : KEY_XBUTTON2);
break;
case WM_LBUTTONUP:
Input_SetReleased(KEY_LMOUSE); break;
case WM_MBUTTONUP:
Input_SetReleased(KEY_MMOUSE); break;
case WM_RBUTTONUP:
Input_SetReleased(KEY_RMOUSE); break;
case WM_XBUTTONUP:
Input_SetReleased(HIWORD(wParam) == 1 ? KEY_XBUTTON1 : KEY_XBUTTON2);
break;
case WM_INPUT:
{
RAWINPUT raw;
UINT ret, rawSize = sizeof(RAWINPUT);
int dx, dy;
ret = _getRawInputData((HRAWINPUT)lParam, RID_INPUT, &raw, &rawSize, sizeof(RAWINPUTHEADER));
if (ret == -1 || raw.header.dwType != RIM_TYPEMOUSE) break;
if (raw.data.mouse.usFlags == MOUSE_MOVE_RELATIVE) {
dx = raw.data.mouse.lLastX;
dy = raw.data.mouse.lLastY;
} else if (raw.data.mouse.usFlags == MOUSE_MOVE_ABSOLUTE) {
static int prevPosX, prevPosY;
dx = raw.data.mouse.lLastX - prevPosX;
dy = raw.data.mouse.lLastY - prevPosY;
prevPosX = raw.data.mouse.lLastX;
prevPosY = raw.data.mouse.lLastY;
} else { break; }
if (Input_RawMode) Event_RaiseRawMove(&PointerEvents.RawMoved, (float)dx, (float)dy);
} break;
case WM_KEYDOWN:
case WM_KEYUP:
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
{
cc_bool pressed = message == WM_KEYDOWN || message == WM_SYSKEYDOWN;
/* Shift/Control/Alt behave strangely when e.g. ShiftRight is held down and ShiftLeft is pressed
and released. It looks like neither key is released in this case, or that the wrong key is
released in the case of Control and Alt.
To combat this, we are going to release both keys when either is released. Hacky, but should work.
Win95 does not distinguish left/right key constants (GetAsyncKeyState returns 0).
In this case, both keys will be reported as pressed. */
cc_bool lShiftDown, rShiftDown;
int key;
if (wParam == VK_SHIFT) {
/* The behavior of this key is very strange. Unlike Control and Alt, there is no extended bit
to distinguish between left and right keys. Moreover, pressing both keys and releasing one
may result in both keys being held down (but not always).*/
lShiftDown = ((USHORT)GetKeyState(VK_LSHIFT)) >> 15;
rShiftDown = ((USHORT)GetKeyState(VK_RSHIFT)) >> 15;
if (!pressed || lShiftDown != rShiftDown) {
Input_Set(KEY_LSHIFT, lShiftDown);
Input_Set(KEY_RSHIFT, rShiftDown);
}
} else {
key = MapNativeKey(wParam, lParam);
if (key) Input_Set(key, pressed);
else Platform_Log1("Unknown key: %x", &wParam);
}
return 0;
} break;
case WM_SYSCHAR:
return 0;
case WM_KILLFOCUS:
/* TODO: Keep track of keyboard when focus is lost */
Input_Clear();
break;
case WM_CLOSE:
Event_RaiseVoid(&WindowEvents.Closing);
if (WindowInfo.Exists) DestroyWindow(win_handle);
WindowInfo.Exists = false;
break;
case WM_DESTROY:
WindowInfo.Exists = false;
UnregisterClassW(CC_WIN_CLASSNAME, win_instance);
if (win_DC) ReleaseDC(win_handle, win_DC);
break;
}
return is_ansiWindow ? DefWindowProcA(handle, message, wParam, lParam)
: DefWindowProcW(handle, message, wParam, lParam);
}
/*########################################################################################################################*
*--------------------------------------------------Public implementation--------------------------------------------------*
*#########################################################################################################################*/
void Window_Init(void) {
HDC hdc = GetDC(NULL);
DisplayInfo.Width = GetSystemMetrics(SM_CXSCREEN);
DisplayInfo.Height = GetSystemMetrics(SM_CYSCREEN);
DisplayInfo.Depth = GetDeviceCaps(hdc, BITSPIXEL);
DisplayInfo.ScaleX = GetDeviceCaps(hdc, LOGPIXELSX) / 96.0f;
DisplayInfo.ScaleY = GetDeviceCaps(hdc, LOGPIXELSY) / 96.0f;
ReleaseDC(NULL, hdc);
}
static ATOM DoRegisterClass(void) {
ATOM atom;
WNDCLASSEXW wc = { 0 };
wc.cbSize = sizeof(WNDCLASSEXW);
wc.style = CS_OWNDC;
wc.hInstance = win_instance;
wc.lpfnWndProc = Window_Procedure;
wc.lpszClassName = CC_WIN_CLASSNAME;
wc.hIcon = (HICON)LoadImageA(win_instance, MAKEINTRESOURCEA(1), IMAGE_ICON,
GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), 0);
wc.hIconSm = (HICON)LoadImageA(win_instance, MAKEINTRESOURCEA(1), IMAGE_ICON,
GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0);
wc.hCursor = LoadCursorA(NULL, IDC_ARROW);
if ((atom = RegisterClassExW(&wc))) return atom;
/* Windows 9x does not support W API functions */
return RegisterClassExA((const WNDCLASSEXA*)&wc);
}
static void DoCreateWindow(ATOM atom, int width, int height) {
cc_result res;
RECT r;
/* Calculate final window rectangle after window decorations are added (titlebar, borders etc) */
r.left = Display_CentreX(width); r.right = r.left + width;
r.top = Display_CentreY(height); r.bottom = r.top + height;
AdjustWindowRect(&r, CC_WIN_STYLE, false);
if ((win_handle = CreateWindowExW(0, MAKEINTATOM(atom), NULL, CC_WIN_STYLE,
r.left, r.top, Rect_Width(r), Rect_Height(r), NULL, NULL, win_instance, NULL))) return;
res = GetLastError();
/* Windows 9x does not support W API functions */
if (res == ERROR_CALL_NOT_IMPLEMENTED) {
is_ansiWindow = true;
if ((win_handle = CreateWindowExA(0, MAKEINTATOM(atom), NULL, CC_WIN_STYLE,
r.left, r.top, Rect_Width(r), Rect_Height(r), NULL, NULL, win_instance, NULL))) return;
res = GetLastError();
}
Logger_Abort2(res, "Failed to create window");
}
void Window_Create(int width, int height) {
ATOM atom;
win_instance = GetModuleHandleA(NULL);
/* TODO: UngroupFromTaskbar(); */
width = Display_ScaleX(width);
height = Display_ScaleY(height);
atom = DoRegisterClass();
DoCreateWindow(atom, width, height);
RefreshWindowBounds();
win_DC = GetDC(win_handle);
if (!win_DC) Logger_Abort2(GetLastError(), "Failed to get device context");
WindowInfo.Exists = true;
WindowInfo.Handle = win_handle;
}
void Window_SetTitle(const cc_string* title) {
WCHAR str[NATIVE_STR_LEN];
Platform_EncodeUtf16(str, title);
if (SetWindowTextW(win_handle, str)) return;
/* Windows 9x does not support W API functions */
Platform_Utf16ToAnsi(str);
SetWindowTextA(win_handle, (const char*)str);
}
void Clipboard_GetText(cc_string* value) {
cc_bool unicode;
HANDLE hGlobal;
LPVOID src;
SIZE_T size;
int i;
/* retry up to 50 times */
for (i = 0; i < 50; i++) {
if (!OpenClipboard(win_handle)) {
Thread_Sleep(10);
continue;
}
unicode = true;
hGlobal = GetClipboardData(CF_UNICODETEXT);
if (!hGlobal) {
hGlobal = GetClipboardData(CF_TEXT);
unicode = false;
}
if (!hGlobal) { CloseClipboard(); return; }
src = GlobalLock(hGlobal);
size = GlobalSize(hGlobal);
/* ignore trailing NULL at end */
/* TODO: Verify it's always there */
if (unicode) {
String_AppendUtf16(value, src, size - 2);
} else {
String_DecodeCP1252(value, src, size - 1);
}
GlobalUnlock(hGlobal);
CloseClipboard();
return;
}
}
void Clipboard_SetText(const cc_string* value) {
cc_unichar* text;
HANDLE hGlobal;
int i;
/* retry up to 10 times */
for (i = 0; i < 10; i++) {
if (!OpenClipboard(win_handle)) {
Thread_Sleep(100);
continue;
}
hGlobal = GlobalAlloc(GMEM_MOVEABLE, (value->length + 1) * 2);
if (!hGlobal) { CloseClipboard(); return; }
text = (cc_unichar*)GlobalLock(hGlobal);
for (i = 0; i < value->length; i++, text++) {
*text = Convert_CP437ToUnicode(value->buffer[i]);
}
*text = '\0';
GlobalUnlock(hGlobal);
EmptyClipboard();
SetClipboardData(CF_UNICODETEXT, hGlobal);
CloseClipboard();
return;
}
}
void Window_Show(void) {
ShowWindow(win_handle, SW_SHOW);
BringWindowToTop(win_handle);
SetForegroundWindow(win_handle);
}
int Window_GetWindowState(void) {
DWORD s = GetWindowLongW(win_handle, GWL_STYLE);
if ((s & WS_MINIMIZE)) return WINDOW_STATE_MINIMISED;
if ((s & WS_MAXIMIZE) && (s & WS_POPUP)) return WINDOW_STATE_FULLSCREEN;
return WINDOW_STATE_NORMAL;
}
static void ToggleFullscreen(cc_bool fullscreen, UINT finalShow) {
DWORD style = WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
style |= (fullscreen ? WS_POPUP : WS_OVERLAPPEDWINDOW);
suppress_resize = true;
{
ShowWindow(win_handle, SW_RESTORE); /* reset maximised state */
SetWindowLongW(win_handle, GWL_STYLE, style);
SetWindowPos(win_handle, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
ShowWindow(win_handle, finalShow);
Window_ProcessEvents();
}
suppress_resize = false;
/* call Resized event only once */
RefreshWindowBounds();
Event_RaiseVoid(&WindowEvents.Resized);
}
static UINT win_show;
cc_result Window_EnterFullscreen(void) {
WINDOWPLACEMENT w = { 0 };
w.length = sizeof(WINDOWPLACEMENT);
GetWindowPlacement(win_handle, &w);
win_show = w.showCmd;
ToggleFullscreen(true, SW_MAXIMIZE);
return 0;
}
cc_result Window_ExitFullscreen(void) {
ToggleFullscreen(false, win_show);
return 0;
}
void Window_SetSize(int width, int height) {
DWORD style = GetWindowLongW(win_handle, GWL_STYLE);
RECT rect = { 0, 0, width, height };
AdjustWindowRect(&rect, style, false);
SetWindowPos(win_handle, NULL, 0, 0,
Rect_Width(rect), Rect_Height(rect), SWP_NOMOVE);
}
void Window_Close(void) {
PostMessageW(win_handle, WM_CLOSE, 0, 0);
}
void Window_ProcessEvents(void) {
HWND foreground;
MSG msg;
if (is_ansiWindow) {
while (PeekMessageA(&msg, NULL, 0, 0, 1)) {
TranslateMessage(&msg); DispatchMessageA(&msg);
}
} else {
while (PeekMessageW(&msg, NULL, 0, 0, 1)) {
TranslateMessage(&msg); DispatchMessageW(&msg);
}
}
foreground = GetForegroundWindow();
if (foreground) {
WindowInfo.Focused = foreground == win_handle;
}
}
static void Cursor_GetRawPos(int* x, int* y) {
POINT point;
GetCursorPos(&point);
*x = point.x; *y = point.y;
}
void Cursor_SetPosition(int x, int y) {
SetCursorPos(x + windowX, y + windowY);
}
static void Cursor_DoSetVisible(cc_bool visible) {
int i;
/* ShowCursor actually is a counter (returns > 0 if visible, <= 0 if not) */
/* Try multiple times in case cursor count was changed by something else */
if (visible) {
for (i = 0; i < 10 && ShowCursor(true) < 0; i++) { }
} else {
for (i = 0; i < 10 && ShowCursor(false) >= 0; i++) {}
}
}
static void ShowDialogCore(const char* title, const char* msg) {
MessageBoxA(win_handle, msg, title, 0);
}
static HDC draw_DC;
static HBITMAP draw_DIB;
void Window_AllocFramebuffer(struct Bitmap* bmp) {
BITMAPINFO hdr = { 0 };
if (!draw_DC) draw_DC = CreateCompatibleDC(win_DC);
/* sizeof(BITMAPINFO) does not work on Windows 9x */
hdr.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
hdr.bmiHeader.biWidth = bmp->width;
hdr.bmiHeader.biHeight = -bmp->height;
hdr.bmiHeader.biBitCount = 32;
hdr.bmiHeader.biPlanes = 1;
draw_DIB = CreateDIBSection(draw_DC, &hdr, DIB_RGB_COLORS, (void**)&bmp->scan0, NULL, 0);
if (!draw_DIB) Logger_Abort2(GetLastError(), "Failed to create DIB");
}
void Window_DrawFramebuffer(Rect2D r) {
HGDIOBJ oldSrc = SelectObject(draw_DC, draw_DIB);
BitBlt(win_DC, r.X, r.Y, r.Width, r.Height, draw_DC, r.X, r.Y, SRCCOPY);
SelectObject(draw_DC, oldSrc);
}
void Window_FreeFramebuffer(struct Bitmap* bmp) {
DeleteObject(draw_DIB);
}
static cc_bool rawMouseInited, rawMouseSupported;
static void InitRawMouse(void) {
static const cc_string user32 = String_FromConst("USER32.DLL");
void* lib;
RAWINPUTDEVICE rid;
if ((lib = DynamicLib_Load2(&user32))) {
_registerRawInput = (FUNC_RegisterRawInput)DynamicLib_Get2(lib, "RegisterRawInputDevices");
_getRawInputData = (FUNC_GetRawInputData) DynamicLib_Get2(lib, "GetRawInputData");
rawMouseSupported = _registerRawInput && _getRawInputData;
}
rawMouseSupported &= Options_GetBool(OPT_RAW_INPUT, true);
if (!rawMouseSupported) { Platform_LogConst("## Raw input unsupported!"); return; }
rid.usUsagePage = 1; /* HID_USAGE_PAGE_GENERIC; */
rid.usUsage = 2; /* HID_USAGE_GENERIC_MOUSE; */
rid.dwFlags = RIDEV_INPUTSINK;
rid.hwndTarget = win_handle;
if (_registerRawInput(&rid, 1, sizeof(rid))) return;
Logger_SysWarn(GetLastError(), "initing raw mouse");
rawMouseSupported = false;
}
void Window_OpenKeyboard(const struct OpenKeyboardArgs* args) { }
void Window_SetKeyboardText(const cc_string* text) { }
void Window_CloseKeyboard(void) { }
void Window_EnableRawMouse(void) {
DefaultEnableRawMouse();
if (!rawMouseInited) InitRawMouse();
rawMouseInited = true;
}
void Window_UpdateRawMouse(void) {
if (rawMouseSupported) {
/* handled in WM_INPUT messages */
CentreMousePosition();
} else {
DefaultUpdateRawMouse();
}
}
void Window_DisableRawMouse(void) { DefaultDisableRawMouse(); }
/*########################################################################################################################*
*-------------------------------------------------------WGL OpenGL--------------------------------------------------------*
*#########################################################################################################################*/
#if defined CC_BUILD_GL && !defined CC_BUILD_EGL
static HGLRC ctx_handle;
static HDC ctx_DC;
typedef BOOL (WINAPI *FP_SWAPINTERVAL)(int interval);
static FP_SWAPINTERVAL wglSwapIntervalEXT;
static void GLContext_SelectGraphicsMode(struct GraphicsMode* mode) {
PIXELFORMATDESCRIPTOR pfd = { 0 };
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER;
/* TODO: PFD_SUPPORT_COMPOSITION FLAG? CHECK IF IT WORKS ON XP */
pfd.cColorBits = mode->R + mode->G + mode->B;
pfd.cDepthBits = GLCONTEXT_DEFAULT_DEPTH;
pfd.iPixelType = mode->IsIndexed ? PFD_TYPE_COLORINDEX : PFD_TYPE_RGBA;
pfd.cRedBits = mode->R;
pfd.cGreenBits = mode->G;
pfd.cBlueBits = mode->B;
pfd.cAlphaBits = mode->A;
int modeIndex = ChoosePixelFormat(win_DC, &pfd);
if (modeIndex == 0) { Logger_Abort("Requested graphics mode not available"); }
Mem_Set(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
DescribePixelFormat(win_DC, modeIndex, pfd.nSize, &pfd);
if (!SetPixelFormat(win_DC, modeIndex, &pfd)) {
Logger_Abort2(GetLastError(), "SetPixelFormat failed");
}
}
void GLContext_Create(void) {
struct GraphicsMode mode;
InitGraphicsMode(&mode);
GLContext_SelectGraphicsMode(&mode);
ctx_handle = wglCreateContext(win_DC);
if (!ctx_handle) ctx_handle = wglCreateContext(win_DC);
if (!ctx_handle) {
Logger_Abort2(GetLastError(), "Failed to create OpenGL context");
}
if (!wglMakeCurrent(win_DC, ctx_handle)) {
Logger_Abort2(GetLastError(), "Failed to make OpenGL context current");
}
ctx_DC = wglGetCurrentDC();
wglSwapIntervalEXT = (FP_SWAPINTERVAL)GLContext_GetAddress("wglSwapIntervalEXT");
}
void GLContext_Update(void) { }
cc_bool GLContext_TryRestore(void) { return true; }
void GLContext_Free(void) {
if (!ctx_handle) return;
wglDeleteContext(ctx_handle);
ctx_handle = NULL;
}
void* GLContext_GetAddress(const char* function) {
void* addr = (void*)wglGetProcAddress(function);
return GLContext_IsInvalidAddress(addr) ? NULL : addr;
}
cc_bool GLContext_SwapBuffers(void) {
if (!SwapBuffers(ctx_DC)) Logger_Abort2(GetLastError(), "Failed to swap buffers");
return true;
}
void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
if (!wglSwapIntervalEXT) return;
wglSwapIntervalEXT(vsync);
}
void GLContext_GetApiInfo(cc_string* info) { }
#endif
#endif

1241
src/Window_X11.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@
#include "Input.h"
#include "Event.h"
#include "Logger.h"
#include "Platform.h"
struct _DisplayData DisplayInfo;
struct _WinData WindowInfo;
@ -9,18 +10,13 @@ struct _WinData WindowInfo;
int Display_ScaleX(int x) { return (int)(x * DisplayInfo.ScaleX); }
int Display_ScaleY(int y) { return (int)(y * DisplayInfo.ScaleY); }
#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
#if 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 */
/* All other platforms implement in the file including this .h file */
#define CC_MAYBE_OBJC1 static
#define CC_MAYBE_OBJC2 static
#define CC_OBJC_VISIBLE static
@ -112,3 +108,107 @@ static void InitGraphicsMode(struct GraphicsMode* m) {
Logger_Abort2(bpp, "Unsupported bits per pixel"); break;
}
}
#ifdef CC_BUILD_GL
/* OpenGL contexts are heavily tied to the window, so for simplicitly are also included here */
/* EGL is window system agnostic, other OpenGL context backends are tied to one windowing system. */
#define GLContext_IsInvalidAddress(ptr) (ptr == (void*)0 || ptr == (void*)1 || ptr == (void*)-1 || ptr == (void*)2)
void GLContext_GetAll(const struct DynamicLibSym* syms, int count) {
int i;
for (i = 0; i < count; i++) {
*syms[i].symAddr = GLContext_GetAddress(syms[i].name);
}
}
/*########################################################################################################################*
*-------------------------------------------------------EGL OpenGL--------------------------------------------------------*
*#########################################################################################################################*/
#if defined CC_BUILD_EGL
#include <EGL/egl.h>
static EGLDisplay ctx_display;
static EGLContext ctx_context;
static EGLSurface ctx_surface;
static EGLConfig ctx_config;
static EGLint ctx_numConfig;
static void GLContext_InitSurface(void) {
if (!win_handle) return; /* window not created or lost */
ctx_surface = eglCreateWindowSurface(ctx_display, ctx_config, win_handle, NULL);
if (!ctx_surface) return;
eglMakeCurrent(ctx_display, ctx_surface, ctx_surface, ctx_context);
}
static void GLContext_FreeSurface(void) {
if (!ctx_surface) return;
eglMakeCurrent(ctx_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroySurface(ctx_display, ctx_surface);
ctx_surface = NULL;
}
void GLContext_Create(void) {
static EGLint contextAttribs[3] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
static EGLint attribs[19] = {
EGL_RED_SIZE, 0, EGL_GREEN_SIZE, 0,
EGL_BLUE_SIZE, 0, EGL_ALPHA_SIZE, 0,
EGL_DEPTH_SIZE, GLCONTEXT_DEFAULT_DEPTH,
EGL_STENCIL_SIZE, 0,
EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_NONE
};
struct GraphicsMode mode;
InitGraphicsMode(&mode);
attribs[1] = mode.R; attribs[3] = mode.G;
attribs[5] = mode.B; attribs[7] = mode.A;
ctx_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(ctx_display, NULL, NULL);
eglBindAPI(EGL_OPENGL_ES_API);
eglChooseConfig(ctx_display, attribs, &ctx_config, 1, &ctx_numConfig);
ctx_context = eglCreateContext(ctx_display, ctx_config, EGL_NO_CONTEXT, contextAttribs);
GLContext_InitSurface();
}
void GLContext_Update(void) {
GLContext_FreeSurface();
GLContext_InitSurface();
}
cc_bool GLContext_TryRestore(void) {
GLContext_FreeSurface();
GLContext_InitSurface();
return ctx_surface != NULL;
}
void GLContext_Free(void) {
GLContext_FreeSurface();
eglDestroyContext(ctx_display, ctx_context);
eglTerminate(ctx_display);
}
void* GLContext_GetAddress(const char* function) {
return eglGetProcAddress(function);
}
cc_bool GLContext_SwapBuffers(void) {
EGLint err;
if (!ctx_surface) return false;
if (eglSwapBuffers(ctx_display, ctx_surface)) return true;
err = eglGetError();
/* TODO: figure out what errors need to be handled here */
Logger_Abort2(err, "Failed to swap buffers");
return false;
}
void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
eglSwapInterval(ctx_display, vsync);
}
void GLContext_GetApiInfo(cc_string* info) { }
#endif
#endif

View File

@ -1,4 +1,4 @@
#include "Window.h"
#include "_WindowBase.h"
#include "Platform.h"
#include "String.h"
#include "Errors.h"