Mouse becoming "unstuck" when resizing windows fixed.

This commit is contained in:
Scott Duensing 2026-03-19 00:41:17 -05:00
parent f599ea8c0d
commit a3e7292591
8 changed files with 107 additions and 32 deletions

View file

@ -794,7 +794,15 @@ static void dispatchEvents(AppContextT *ctx) {
// Handle active resize // Handle active resize
if (ctx->stack.resizeWindow >= 0) { if (ctx->stack.resizeWindow >= 0) {
if (buttons & MOUSE_LEFT) { 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 { } else {
wmResizeEnd(&ctx->stack); wmResizeEnd(&ctx->stack);
} }

View file

@ -899,6 +899,10 @@ void wgtSetEnabled(WidgetT *w, bool enabled);
// Set read-only mode (allows scrolling/selection but blocks editing) // Set read-only mode (allows scrolling/selection but blocks editing)
void wgtSetReadOnly(WidgetT *w, bool readOnly); void wgtSetReadOnly(WidgetT *w, bool readOnly);
// Set/get keyboard focus
void wgtSetFocused(WidgetT *w);
WidgetT *wgtGetFocused(void);
// Show/hide a widget // Show/hide a widget
void wgtSetVisible(WidgetT *w, bool visible); void wgtSetVisible(WidgetT *w, bool visible);

View file

@ -2011,14 +2011,16 @@ void wmResizeEnd(WindowStackT *stack) {
// which would be confusing — the user's manual resize represents their // which would be confusing — the user's manual resize represents their
// new intent. // 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) { if (stack->resizeWindow < 0 || stack->resizeWindow >= stack->count) {
return; return;
} }
WindowT *win = stack->windows[stack->resizeWindow]; WindowT *win = stack->windows[stack->resizeWindow];
int32_t dx = mouseX - stack->dragOffX; int32_t mx = *mouseX;
int32_t dy = mouseY - stack->dragOffY; int32_t my = *mouseY;
int32_t dx = mx - stack->dragOffX;
int32_t dy = my - stack->dragOffY;
// Compute dynamic minimum size for this window // Compute dynamic minimum size for this window
int32_t minW; int32_t minW;
@ -2032,13 +2034,6 @@ void wmResizeMove(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, int32_
// Mark old position dirty // Mark old position dirty
dirtyListAdd(dl, win->x, win->y, win->w, win->h); 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) { if (stack->resizeEdge & RESIZE_LEFT) {
int32_t newX = win->x + dx; int32_t newX = win->x + dx;
int32_t newW = win->w - dx; int32_t newW = win->w - dx;
@ -2053,11 +2048,14 @@ void wmResizeMove(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, int32_
newX = 0; newX = 0;
} }
if (newW >= minW) { if (newW < minW) {
newW = minW;
newX = win->x + win->w - minW;
}
win->x = newX; win->x = newX;
win->w = newW; win->w = newW;
appliedX = true; mx = newX;
}
} }
if (stack->resizeEdge & RESIZE_RIGHT) { if (stack->resizeEdge & RESIZE_RIGHT) {
@ -2071,10 +2069,12 @@ void wmResizeMove(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, int32_
newW = d->width - win->x; newW = d->width - win->x;
} }
if (newW >= minW) { if (newW < minW) {
win->w = newW; newW = minW;
appliedX = true;
} }
win->w = newW;
mx = win->x + newW;
} }
if (stack->resizeEdge & RESIZE_TOP) { if (stack->resizeEdge & RESIZE_TOP) {
@ -2091,11 +2091,14 @@ void wmResizeMove(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, int32_
newY = 0; newY = 0;
} }
if (newH >= minH) { if (newH < minH) {
newH = minH;
newY = win->y + win->h - minH;
}
win->y = newY; win->y = newY;
win->h = newH; win->h = newH;
appliedY = true; my = newY;
}
} }
if (stack->resizeEdge & RESIZE_BOTTOM) { if (stack->resizeEdge & RESIZE_BOTTOM) {
@ -2109,10 +2112,12 @@ void wmResizeMove(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, int32_
newH = d->height - win->y; newH = d->height - win->y;
} }
if (newH >= minH) { if (newH < minH) {
win->h = newH; newH = minH;
appliedY = true;
} }
win->h = newH;
my = win->y + newH;
} }
// If resized while maximized, consider it no longer maximized // 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; win->contentDirty = true;
} }
if (appliedX) { // Always update dragOff to the clamped position so the next delta
stack->dragOffX = mouseX; // is computed from the edge, not from where the mouse wandered.
} stack->dragOffX = mx;
stack->dragOffY = my;
if (appliedY) { // Report clamped position back so the caller can warp the cursor
stack->dragOffY = mouseY; *mouseX = mx;
} *mouseY = my;
// Mark new position dirty // Mark new position dirty
dirtyListAdd(dl, win->x, win->y, win->w, win->h); dirtyListAdd(dl, win->x, win->y, win->w, win->h);

View file

@ -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 // Update window dimensions during an active resize. Enforces MIN_WINDOW_W/H
// and optional maxW/maxH constraints. Reallocates the content buffer if the // and optional maxW/maxH constraints. Reallocates the content buffer if the
// content area size changed, then calls onResize to notify the application. // 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 // Begin a window drag operation. Records the mouse offset from the window
// origin so the window doesn't jump to the cursor position. // origin so the window doesn't jump to the cursor position.

View file

@ -139,6 +139,11 @@ bool platformMouseWheelInit(void);
// driver between polls). // driver between polls).
int32_t platformMouseWheelPoll(void); 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 // Input — Keyboard
// ============================================================ // ============================================================

View file

@ -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 // platformSpanCopy8
// ============================================================ // ============================================================

View file

@ -454,6 +454,15 @@ void wgtSetDebugLayout(AppContextT *ctx, bool enabled) {
} }
// ============================================================
// wgtGetFocused
// ============================================================
WidgetT *wgtGetFocused(void) {
return sFocusedWidget;
}
// ============================================================ // ============================================================
// wgtSetEnabled // 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 // wgtSetReadOnly
// ============================================================ // ============================================================

View file

@ -352,7 +352,9 @@ DXE_EXPORT_TABLE(shellExportTable)
DXE_EXPORT(wgtSetText) DXE_EXPORT(wgtSetText)
DXE_EXPORT(wgtSetTooltip) DXE_EXPORT(wgtSetTooltip)
DXE_EXPORT(wgtGetText) DXE_EXPORT(wgtGetText)
DXE_EXPORT(wgtGetFocused)
DXE_EXPORT(wgtSetEnabled) DXE_EXPORT(wgtSetEnabled)
DXE_EXPORT(wgtSetFocused)
DXE_EXPORT(wgtSetReadOnly) DXE_EXPORT(wgtSetReadOnly)
DXE_EXPORT(wgtSetVisible) DXE_EXPORT(wgtSetVisible)
DXE_EXPORT(wgtGetContext) DXE_EXPORT(wgtGetContext)