Dragging a Desktop Pet in Rust: The HWND_TOP Trap That Silently Kills Always-On-Top
Any Windows Rust project that mixes a windowing framework with direct Win32 calls can accidentally overwrite properties the framework set. HWND_TOP vs HWND_TOPMOST is a one-word difference that silently breaks always-on-top, and the failure only becomes visible when another window opens—making it easy to ship broken.
Building a tiny borderless, transparent, always-on-top desktop pet window in Rust with winit and softbuffer ran into three drag problems. The most obvious were visual ghosting from software rendering presents colliding with window moves, and a slight lag from coordinate truncation. The real bug was subtler: every drag called SetWindowPos with HWND_TOP, which silently demoted the window from the topmost Z-order layer back to the normal layer, breaking always-on-top. The fix uses HWND_TOPMOST, decouples redraws from moves via a size-change guard, switches to GetCursorPos for screen-coordinate consistency, and clamps the window to the monitor work area.
The least visible symptom—always-on-top silently breaking—was the only actual correctness bug; the visual glitches were performance artifacts. This inversion of severity is common when native API calls quietly override framework state.
HWND_TOP and HWND_TOPMOST differ by only a few characters but have opposite effects on Z-order; the constant name suggests ‘top’ but actually means ‘top of the normal heap, not the topmost heap.’
The SC_MOVE trick is widely shared as a universal drag fix, but it depends on a synchronous message-loop context that event-driven frameworks like winit do not provide—making it a trap for Rust GUI developers.
Decoupling move from redraw via a size-change check is a minimal, low-risk pattern that avoids both ghosting and content loss, and generalizes to any software-rendered overlay window.