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
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);
}

View file

@ -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);

View file

@ -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) {
if (newW < minW) {
newW = minW;
newX = win->x + win->w - minW;
}
win->x = newX;
win->w = newW;
appliedX = true;
}
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) {
if (newH < minH) {
newH = minH;
newY = win->y + win->h - minH;
}
win->y = newY;
win->h = newH;
appliedY = true;
}
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);

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
// 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.

View file

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

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

View file

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

View file

@ -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)