mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-09-15 18:45:23 -04:00
Split up Window.c into separate backends, except for mac
This commit is contained in:
parent
bf64700292
commit
df6cc32cdf
@ -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" />
|
||||
|
@ -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>
|
@ -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__
|
||||
|
3434
src/Window.c
3434
src/Window.c
File diff suppressed because it is too large
Load Diff
410
src/Window_Android.c
Normal file
410
src/Window_Android.c
Normal 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
358
src/Window_SDL.c
Normal 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
651
src/Window_Web.c
Normal 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
667
src/Window_Win.c
Normal 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
1241
src/Window_X11.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
@ -1,4 +1,4 @@
|
||||
#include "Window.h"
|
||||
#include "_WindowBase.h"
|
||||
#include "Platform.h"
|
||||
#include "String.h"
|
||||
#include "Errors.h"
|
||||
|
Loading…
x
Reference in New Issue
Block a user