From a3e7292591ca4d81f63afee773dd538ab2a574ce Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Thu, 19 Mar 2026 00:41:17 -0500 Subject: [PATCH] Mouse becoming "unstuck" when resizing windows fixed. --- dvx/dvxApp.c | 10 +++++- dvx/dvxWidget.h | 4 +++ dvx/dvxWm.c | 66 +++++++++++++++++++---------------- dvx/dvxWm.h | 4 ++- dvx/platform/dvxPlatform.h | 5 +++ dvx/platform/dvxPlatformDos.c | 19 ++++++++++ dvx/widgets/widgetOps.c | 29 +++++++++++++++ dvxshell/shellExport.c | 2 ++ 8 files changed, 107 insertions(+), 32 deletions(-) diff --git a/dvx/dvxApp.c b/dvx/dvxApp.c index c286687..2da43c0 100644 --- a/dvx/dvxApp.c +++ b/dvx/dvxApp.c @@ -794,7 +794,15 @@ static void dispatchEvents(AppContextT *ctx) { // Handle active resize if (ctx->stack.resizeWindow >= 0) { if (buttons & MOUSE_LEFT) { - wmResizeMove(&ctx->stack, &ctx->dirty, &ctx->display, mx, my); + int32_t clampX = mx; + int32_t clampY = my; + wmResizeMove(&ctx->stack, &ctx->dirty, &ctx->display, &clampX, &clampY); + + if (clampX != mx || clampY != my) { + platformMouseWarp(clampX, clampY); + ctx->mouseX = clampX; + ctx->mouseY = clampY; + } } else { wmResizeEnd(&ctx->stack); } diff --git a/dvx/dvxWidget.h b/dvx/dvxWidget.h index 84f3e6d..656cac1 100644 --- a/dvx/dvxWidget.h +++ b/dvx/dvxWidget.h @@ -899,6 +899,10 @@ void wgtSetEnabled(WidgetT *w, bool enabled); // Set read-only mode (allows scrolling/selection but blocks editing) void wgtSetReadOnly(WidgetT *w, bool readOnly); +// Set/get keyboard focus +void wgtSetFocused(WidgetT *w); +WidgetT *wgtGetFocused(void); + // Show/hide a widget void wgtSetVisible(WidgetT *w, bool visible); diff --git a/dvx/dvxWm.c b/dvx/dvxWm.c index 3972fc6..e8268e1 100644 --- a/dvx/dvxWm.c +++ b/dvx/dvxWm.c @@ -2011,14 +2011,16 @@ void wmResizeEnd(WindowStackT *stack) { // which would be confusing — the user's manual resize represents their // new intent. -void wmResizeMove(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, int32_t mouseX, int32_t mouseY) { +void wmResizeMove(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, int32_t *mouseX, int32_t *mouseY) { if (stack->resizeWindow < 0 || stack->resizeWindow >= stack->count) { return; } WindowT *win = stack->windows[stack->resizeWindow]; - int32_t dx = mouseX - stack->dragOffX; - int32_t dy = mouseY - stack->dragOffY; + int32_t mx = *mouseX; + int32_t my = *mouseY; + int32_t dx = mx - stack->dragOffX; + int32_t dy = my - stack->dragOffY; // Compute dynamic minimum size for this window int32_t minW; @@ -2032,13 +2034,6 @@ void wmResizeMove(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, int32_ // Mark old position dirty dirtyListAdd(dl, win->x, win->y, win->w, win->h); - // Track whether each axis actually changed, so we only update - // dragOff on axes where the resize was applied. If clamped, leaving - // dragOff unchanged makes the border "stick" to the mouse when the - // user reverses direction, instead of creating a dead zone. - bool appliedX = false; - bool appliedY = false; - if (stack->resizeEdge & RESIZE_LEFT) { int32_t newX = win->x + dx; int32_t newW = win->w - dx; @@ -2053,11 +2048,14 @@ void wmResizeMove(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, int32_ newX = 0; } - if (newW >= minW) { - win->x = newX; - win->w = newW; - appliedX = true; + if (newW < minW) { + newW = minW; + newX = win->x + win->w - minW; } + + win->x = newX; + win->w = newW; + mx = newX; } if (stack->resizeEdge & RESIZE_RIGHT) { @@ -2071,10 +2069,12 @@ void wmResizeMove(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, int32_ newW = d->width - win->x; } - if (newW >= minW) { - win->w = newW; - appliedX = true; + if (newW < minW) { + newW = minW; } + + win->w = newW; + mx = win->x + newW; } if (stack->resizeEdge & RESIZE_TOP) { @@ -2091,11 +2091,14 @@ void wmResizeMove(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, int32_ newY = 0; } - if (newH >= minH) { - win->y = newY; - win->h = newH; - appliedY = true; + if (newH < minH) { + newH = minH; + newY = win->y + win->h - minH; } + + win->y = newY; + win->h = newH; + my = newY; } if (stack->resizeEdge & RESIZE_BOTTOM) { @@ -2109,10 +2112,12 @@ void wmResizeMove(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, int32_ newH = d->height - win->y; } - if (newH >= minH) { - win->h = newH; - appliedY = true; + if (newH < minH) { + newH = minH; } + + win->h = newH; + my = win->y + newH; } // If resized while maximized, consider it no longer maximized @@ -2134,13 +2139,14 @@ void wmResizeMove(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, int32_ win->contentDirty = true; } - if (appliedX) { - stack->dragOffX = mouseX; - } + // Always update dragOff to the clamped position so the next delta + // is computed from the edge, not from where the mouse wandered. + stack->dragOffX = mx; + stack->dragOffY = my; - if (appliedY) { - stack->dragOffY = mouseY; - } + // Report clamped position back so the caller can warp the cursor + *mouseX = mx; + *mouseY = my; // Mark new position dirty dirtyListAdd(dl, win->x, win->y, win->w, win->h); diff --git a/dvx/dvxWm.h b/dvx/dvxWm.h index 5960c41..1a34c42 100644 --- a/dvx/dvxWm.h +++ b/dvx/dvxWm.h @@ -136,7 +136,9 @@ void wmDragMove(WindowStackT *stack, DirtyListT *dl, int32_t mouseX, int32_t mou // Update window dimensions during an active resize. Enforces MIN_WINDOW_W/H // and optional maxW/maxH constraints. Reallocates the content buffer if the // content area size changed, then calls onResize to notify the application. -void wmResizeMove(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, int32_t mouseX, int32_t mouseY); +// mouseX/mouseY are in/out: on return they hold the clamped position so the +// caller can warp the hardware cursor to keep it stuck to the window edge. +void wmResizeMove(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, int32_t *mouseX, int32_t *mouseY); // Begin a window drag operation. Records the mouse offset from the window // origin so the window doesn't jump to the cursor position. diff --git a/dvx/platform/dvxPlatform.h b/dvx/platform/dvxPlatform.h index 06d2063..69a2de6 100644 --- a/dvx/platform/dvxPlatform.h +++ b/dvx/platform/dvxPlatform.h @@ -139,6 +139,11 @@ bool platformMouseWheelInit(void); // driver between polls). int32_t platformMouseWheelPoll(void); +// Move the mouse cursor to an absolute screen position. Uses INT 33h +// function 04h on DOS, SDL_WarpMouseInWindow on Linux. Used to clamp +// the cursor to window edges during resize operations. +void platformMouseWarp(int32_t x, int32_t y); + // ============================================================ // Input — Keyboard // ============================================================ diff --git a/dvx/platform/dvxPlatformDos.c b/dvx/platform/dvxPlatformDos.c index 69aa371..2e8dcd8 100644 --- a/dvx/platform/dvxPlatformDos.c +++ b/dvx/platform/dvxPlatformDos.c @@ -1199,6 +1199,25 @@ int32_t platformMouseWheelPoll(void) { } +// ============================================================ +// platformMouseWarp +// ============================================================ +// +// Moves the mouse cursor to an absolute screen position via INT 33h +// function 04h. Used to clamp the cursor to window edges during resize +// so the pointer visually sticks to the border. + +void platformMouseWarp(int32_t x, int32_t y) { + __dpmi_regs r; + + memset(&r, 0, sizeof(r)); + r.x.ax = 0x0004; + r.x.cx = x; + r.x.dx = y; + __dpmi_int(0x33, &r); +} + + // ============================================================ // platformSpanCopy8 // ============================================================ diff --git a/dvx/widgets/widgetOps.c b/dvx/widgets/widgetOps.c index c3c53c1..f3398dc 100644 --- a/dvx/widgets/widgetOps.c +++ b/dvx/widgets/widgetOps.c @@ -454,6 +454,15 @@ void wgtSetDebugLayout(AppContextT *ctx, bool enabled) { } +// ============================================================ +// wgtGetFocused +// ============================================================ + +WidgetT *wgtGetFocused(void) { + return sFocusedWidget; +} + + // ============================================================ // wgtSetEnabled // ============================================================ @@ -466,6 +475,26 @@ void wgtSetEnabled(WidgetT *w, bool enabled) { } +// ============================================================ +// wgtSetFocused +// ============================================================ + +void wgtSetFocused(WidgetT *w) { + if (!w || !w->enabled) { + return; + } + + if (sFocusedWidget && sFocusedWidget != w) { + sFocusedWidget->focused = false; + wgtInvalidatePaint(sFocusedWidget); + } + + w->focused = true; + sFocusedWidget = w; + wgtInvalidatePaint(w); +} + + // ============================================================ // wgtSetReadOnly // ============================================================ diff --git a/dvxshell/shellExport.c b/dvxshell/shellExport.c index 8eb3729..3235f6a 100644 --- a/dvxshell/shellExport.c +++ b/dvxshell/shellExport.c @@ -352,7 +352,9 @@ DXE_EXPORT_TABLE(shellExportTable) DXE_EXPORT(wgtSetText) DXE_EXPORT(wgtSetTooltip) DXE_EXPORT(wgtGetText) + DXE_EXPORT(wgtGetFocused) DXE_EXPORT(wgtSetEnabled) + DXE_EXPORT(wgtSetFocused) DXE_EXPORT(wgtSetReadOnly) DXE_EXPORT(wgtSetVisible) DXE_EXPORT(wgtGetContext)