diff options
| author | KonAki <konaki@anokonaki.org> | 2026-10-10 18:37:04 +0800 |
|---|---|---|
| committer | KonAki <konaki@anokonaki.org> | 2026-10-10 18:37:04 +0800 |
| commit | 0bfbeec1abdf937e3658fe07f75de22fdb2e7bea (patch) | |
| tree | 735916fff4b2884fc5d622c4cc934991e9c3c103 /kon/konwinlib.h | |
| parent | 996dcc5002d1d5961d54d924ae4a44f31898024e (diff) | |
add mouse capture and relative mouse movement
Diffstat (limited to 'kon/konwinlib.h')
| -rw-r--r-- | kon/konwinlib.h | 171 |
1 files changed, 170 insertions, 1 deletions
diff --git a/kon/konwinlib.h b/kon/konwinlib.h index 0a1f365..1570931 100644 --- a/kon/konwinlib.h +++ b/kon/konwinlib.h @@ -110,6 +110,7 @@ typedef struct kon_event { int key; unsigned int codepoint; /* unicode codepoint, only set for KON_EVENT_TEXT */ int mouseX, mouseY; + int mouseDX, mouseDY; /* how far the mouse moved, set for KON_EVENT_MOUSE_MOVE. keeps working while the mouse is captured */ kon_mouseButton_t mouseButton; int wheel; /* only set for KON_EVENT_MOUSE_WHEEL, 1 for each notch away from you and -1 towards you */ } kon_event_t; @@ -128,6 +129,11 @@ void kon_setWindowSize(kon_window_t *window, int width, int height); void kon_getWindowPos(kon_window_t *window, int *x, int *y); void kon_getWindowSize(kon_window_t *window, int *width, int *height); +/* hides the cursor and keeps it in the window so the mouse can turn a camera, move events keep reporting mouseDX and mouseDY. + returns 1 when it worked. the window lets go by itself when it loses focus. on the web this has to be called from a click or key press */ +int kon_setMouseCapture(kon_window_t *window, bool captured); +bool kon_isMouseCaptured(const kon_window_t *window); + /*** window tricks ***/ typedef enum kon_messageType { @@ -182,7 +188,13 @@ static void kon_pushEvent_(kon_eventQueue_t *q, const kon_event_t *event) { if (q->count > 0 && (event->type == KON_EVENT_MOUSE_MOVE || event->type == KON_EVENT_RESIZE)) { kon_event_t *last = &q->events[(q->head + q->count - 1) % KON_EVENT_QUEUE_SIZE]; if (last->type == event->type) { + int dx = last->mouseDX + event->mouseDX, dy = last->mouseDY + event->mouseDY; + *last = *event; + if (event->type == KON_EVENT_MOUSE_MOVE) { + last->mouseDX = dx; + last->mouseDY = dy; + } return; } } @@ -212,6 +224,7 @@ struct kon_context { struct kon_window { char canvasID[64]; + bool captured; bool shouldClose; int exitKey; kon_eventQueue_t queue; @@ -369,6 +382,8 @@ static EM_BOOL kon_mouseCallback_(int eventType, const EmscriptenMouseEvent *e, if (eventType == EMSCRIPTEN_EVENT_MOUSEMOVE) { ev.type = KON_EVENT_MOUSE_MOVE; + ev.mouseDX = (int)e->movementX; + ev.mouseDY = (int)e->movementY; } else { if (e->button > KON_MOUSE_RIGHT) return EM_FALSE; ev.type = (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) ? KON_EVENT_MOUSE_DOWN : KON_EVENT_MOUSE_UP; @@ -428,9 +443,27 @@ kon_window_t *kon_createWindow(const char *title, int x, int y, int width, int h return window; } +int kon_setMouseCapture(kon_window_t *window, bool captured) { + if (!window) return 0; + + if (captured) { + if (emscripten_request_pointerlock(window->canvasID, EM_TRUE) < 0) return 0; + window->captured = true; + } else { + emscripten_exit_pointerlock(); + window->captured = false; + } + return 1; +} + +bool kon_isMouseCaptured(const kon_window_t *window) { + return window && window->captured; +} + void kon_destroyWindow(kon_window_t *window) { if (!window) return; + if (window->captured) emscripten_exit_pointerlock(); emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, NULL); emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, NULL); emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_FALSE, NULL); @@ -702,6 +735,9 @@ struct kon_window { bool shouldClose; int exitKey; int width, height; + bool captured, hasLast; + int lastX, lastY; + Cursor blankCursor; kon_eventQueue_t queue; }; @@ -802,7 +838,7 @@ kon_window_t *kon_createWindow(const char *title, int x, int y, int width, int h } XStoreName(kon_ctx->display, window->window, title); - XSelectInput(kon_ctx->display, window->window, ExposureMask | StructureNotifyMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask); + XSelectInput(kon_ctx->display, window->window, ExposureMask | StructureNotifyMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask); if (flags & KON_WINDOW_NO_DECOR) { Atom motif_hints = XInternAtom(kon_ctx->display, "_MOTIF_WM_HINTS", False); @@ -845,10 +881,51 @@ kon_window_t *kon_createWindow(const char *title, int x, int y, int width, int h return window; } +int kon_setMouseCapture(kon_window_t *window, bool captured) { + if (!window || !kon_ctx) return 0; + + if (!captured) { + if (!window->captured) return 1; + XUngrabPointer(kon_ctx->display, CurrentTime); + if (window->blankCursor) XFreeCursor(kon_ctx->display, window->blankCursor); + window->blankCursor = 0; + window->captured = false; + window->hasLast = false; + XFlush(kon_ctx->display); + return 1; + } + if (window->captured) return 1; + + { + char data[1] = {0}; + XColor black = {0}; + Pixmap blank = XCreateBitmapFromData(kon_ctx->display, window->window, data, 1, 1); + + window->blankCursor = XCreatePixmapCursor(kon_ctx->display, blank, blank, &black, &black, 0, 0); + XFreePixmap(kon_ctx->display, blank); + } + + if (XGrabPointer(kon_ctx->display, window->window, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, + GrabModeAsync, GrabModeAsync, window->window, window->blankCursor, CurrentTime) != GrabSuccess) { + XFreeCursor(kon_ctx->display, window->blankCursor); + window->blankCursor = 0; + return 0; + } + window->captured = true; + XWarpPointer(kon_ctx->display, None, window->window, 0, 0, 0, 0, window->width / 2, window->height / 2); + XFlush(kon_ctx->display); + return 1; +} + +bool kon_isMouseCaptured(const kon_window_t *window) { + return window && window->captured; +} + void kon_destroyWindow(kon_window_t *window) { if (!window) return; if (!kon_ctx) { free(window); return; } + kon_setMouseCapture(window, false); if (window->gc) XFreeGC(kon_ctx->display, window->gc); if (window->window) XDestroyWindow(kon_ctx->display, window->window); if (window->owns_colormap && window->colormap) XFreeColormap(kon_ctx->display, window->colormap); @@ -990,9 +1067,28 @@ static void kon_pumpX11_(kon_window_t *window) { ev.type = KON_EVENT_MOUSE_MOVE; ev.mouseX = xev.xmotion.x; ev.mouseY = xev.xmotion.y; + if (window->captured) { + /* the cursor gets put back in the middle every time, so how far it got from there is how far it moved */ + int cx = window->width / 2, cy = window->height / 2; + + ev.mouseDX = ev.mouseX - cx; + ev.mouseDY = ev.mouseY - cy; + if (ev.mouseDX == 0 && ev.mouseDY == 0) break; + XWarpPointer(kon_ctx->display, None, window->window, 0, 0, 0, 0, cx, cy); + } else if (window->hasLast) { + ev.mouseDX = ev.mouseX - window->lastX; + ev.mouseDY = ev.mouseY - window->lastY; + } + window->lastX = ev.mouseX; + window->lastY = ev.mouseY; + window->hasLast = true; kon_pushEvent_(&window->queue, &ev); break; + case FocusOut: + if (xev.xfocus.mode != NotifyGrab && xev.xfocus.mode != NotifyUngrab) kon_setMouseCapture(window, false); + break; + default: break; } @@ -1499,6 +1595,8 @@ struct kon_window { unsigned char alpha; int exitKey; unsigned short highSurrogate; + bool captured, hasLast; + int lastX, lastY; kon_mainLoopFn liveFn; void *liveData; bool inLive; @@ -1688,9 +1786,32 @@ LRESULT CALLBACK kon_wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) ev.type = KON_EVENT_MOUSE_MOVE; ev.mouseX = (int)(short)LOWORD(lParam); ev.mouseY = (int)(short)HIWORD(lParam); + if (window->captured) { + /* the cursor gets put back in the middle every time, so how far it got from there is how far it moved */ + RECT area; + POINT middle; + + GetClientRect(hwnd, &area); + middle.x = (area.right - area.left) / 2; + middle.y = (area.bottom - area.top) / 2; + ev.mouseDX = ev.mouseX - (int)middle.x; + ev.mouseDY = ev.mouseY - (int)middle.y; + if (ev.mouseDX == 0 && ev.mouseDY == 0) break; + ClientToScreen(hwnd, &middle); + SetCursorPos(middle.x, middle.y); + } else if (window->hasLast) { + ev.mouseDX = ev.mouseX - window->lastX; + ev.mouseDY = ev.mouseY - window->lastY; + } + window->lastX = ev.mouseX; + window->lastY = ev.mouseY; + window->hasLast = true; kon_pushEvent_(&window->queue, &ev); } break; + case WM_KILLFOCUS: + if (window) kon_setMouseCapture(window, false); + break; case WM_CLOSE: if (window) { kon_event_t ev = {0}; @@ -1823,8 +1944,56 @@ kon_window_t *kon_createWindow(const char *title, int x, int y, int width, int h return window; } +int kon_setMouseCapture(kon_window_t *window, bool captured) { + if (!window || !window->hwnd) return 0; + + if (!captured) { + if (!window->captured) return 1; + window->captured = false; + window->hasLast = false; + ClipCursor(NULL); + ReleaseCapture(); + ShowCursor(TRUE); + return 1; + } + if (window->captured) return 1; + if (GetForegroundWindow() != window->hwnd) return 0; + + { + RECT area; + POINT topLeft, bottomRight, middle; + + GetClientRect(window->hwnd, &area); + topLeft.x = area.left; + topLeft.y = area.top; + bottomRight.x = area.right; + bottomRight.y = area.bottom; + ClientToScreen(window->hwnd, &topLeft); + ClientToScreen(window->hwnd, &bottomRight); + area.left = topLeft.x; + area.top = topLeft.y; + area.right = bottomRight.x; + area.bottom = bottomRight.y; + ClipCursor(&area); + + middle.x = (area.right - area.left) / 2; + middle.y = (area.bottom - area.top) / 2; + ClientToScreen(window->hwnd, &middle); + SetCursorPos(middle.x, middle.y); + } + SetCapture(window->hwnd); + ShowCursor(FALSE); + window->captured = true; + return 1; +} + +bool kon_isMouseCaptured(const kon_window_t *window) { + return window && window->captured; +} + void kon_destroyWindow(kon_window_t *window) { if (!window) return; + kon_setMouseCapture(window, false); if (window->hwnd) DestroyWindow(window->hwnd); free(window); } |
