← All projects

A tiny physics engine that makes every window on your Windows desktop fall, bounce and get thrown around.

Year
2026
Built with
C, Win32 API
Source
GitHub ↗
readme.txt

WinGravity finds every visible window on your Windows desktop and gives it mass. Drop them, throw them, watch them bounce off the edges of your screen.

These windows run the same physics, ported line-for-line from the C.

controls.txt
  1. Turn on gravity in the taskbar.
  2. Grab a window by its title bar.
  3. Fling it and let go.
gravity.c
#define GRAVITY 444.0f
#define FRICTION 5.0f
#define BOUNCE 0.6f
#define BOUNCE_THRESHOLD 80.0f
#define BOUNCE_THRESHOLD_GROUND 120.0f
physics.log
waiting for input…
WinGravity

The idea

I wanted to know whether I could make Windows itself feel physical — not a game in a window, but the actual windows on my desktop falling to the bottom of the screen and bouncing when they landed. It turned out the Win32 API gives you everything you need: a list of every window, events when the user drags one, and a way to move any window you like.

How it works

Finding the windows

On start-up, EnumWindows walks every top-level window. Anything that's visible and has a title becomes an EngineObject with a position, velocity and size, stored in an array that grows with realloc.

Listening for the user

SetWinEventHook registers for EVENT_SYSTEM_MOVESIZESTART, EVENT_SYSTEM_MOVESIZEEND and EVENT_OBJECT_LOCATIONCHANGE. When you grab a window, it's marked as grabbed and the physics stops fighting you. While you drag, its position is sampled every 20 frames; when you let go, the distance travelled over that time becomes the throw velocity.

The physics loop

Every frame, the engine measures elapsed time with QueryPerformanceCounter, adds gravity to each window's vertical velocity, and checks it against the edges of the virtual screen (all monitors combined). Hitting the floor bounces the window back up with 60% of its speed — unless it's moving too slowly, in which case it settles and friction takes over. Finally, SetWindowPos moves every real window to its new position.

Bringing it to the browser

The demo above is a TypeScript port of UpdateEnginePhysics. The Win32 parts are replaced — pointer events stand in for the window hooks, CSS transforms stand in for SetWindowPos — but the constants, the collision rules and the throw calculation are the same. Here's the floor collision in both:

gravity.c
//Check if object is on floor
if (objectBottom >= boundary->bottom){
    engine->objects[i].pos.y = (float)(boundary->bottom - engine->objects[i].height);
    if (fabs(engine->objects[i].vel.y) > BOUNCE_THRESHOLD_GROUND){
        engine->objects[i].vel.y *= -BOUNCE;
    }
    else{
        engine->objects[i].vel.y = 0.0f;
    }
    //Apply friction
    if (fabs(engine->objects[i].vel.x) <= FRICTION){
        engine->objects[i].vel.x = 0.0f;
    }
    else {
        engine->objects[i].vel.x -= fsign(engine->objects[i].vel.x) * FRICTION;
    }
}
engine.ts
if (bottom >= bounds.bottom) {
  o.pos.y = bounds.bottom - o.height;
  if (Math.abs(o.vel.y) > BOUNCE_THRESHOLD_GROUND) o.vel.y *= -BOUNCE;
  else o.vel.y = 0;

  if (Math.abs(o.vel.x) <= FRICTION) o.vel.x = 0;
  else o.vel.x -= Math.sign(o.vel.x) * FRICTION;
}

What I learned

  • Callbacks and the Windows message loop. Nothing happens unless you keep pumping messages with PeekMessage, so the physics and the event handling have to share one loop.
  • Fixed timesteps. Running physics in fixed steps with an accumulator keeps the simulation stable no matter how fast or slow the frames come in.
  • Manual memory. Growing the window list with realloc, and checking it didn't fail before using it.