Very cool, looks like a good start! It built/ran right away on my windows machine. A bit “fuzzy” (see my notes on DPI below) but a good starting point.
I’ve been playing around with my own Windowing library and have some insights to share if you’re interested.
When it comes to cross-platform APIs it can be hard to create a good abstraction. In general you have to accomodate the “worst-case” and I’ve found that for windowing, the worst case is “Microsoft Windows” which unfortunately is…callback based. My understanding right now is if you try to abstract away the callback-based nature of the underlying system API it can become a headache to make work properly and most solutions end up sacrificing features/functionality because of it. I actually ran into the same problem with regards to Audio APIs…macOS is callback-based and in order to make an abstraction that works well on all platforms, it became much simpler once I gave up trying to abstract this away and just gave in to making my API callback-based as well. Callback based API’s are harder to use, but, trying to abstract one away is a nightmare.
So right now when I run your code on Windows, if I move/resize the window, the application essentially pauses because the wndproc recurses in on itself an never returns control back to the application loop to update the frames. If the application itself is also callback based then this isn’t a problem because you’ll continue to get your “paint” callbacks even if the main loop doesn’t return. There are ways to trick/workaround this design but you end up losing out on builtin features like window snapping/shake gestures etc, and these features are specific to each version of Windows, attempting to override them and re-implement them for every windows version is a herculean task. Also note that this isn’t just a problem with moving/resizing a window, it’s also a problem with various win32 APIs. Functions like MessageBox
or ShellExecute
also start their own message loop until they are finished. So be careful not to customize or at least depend on a custom message loop.
Your API is similar to those I’ve seen that are usually meant for games, and, many times games can get away with this simpler API because it’s ok if they don’t interact well with the Desktop Compositor. It’s ok if they don’t update their window content right away because they’re just going to update it in 10 ms anyway, a little bit of artifacting while resizing/moving the window isn’t a huge deal because most of the time the user is just playing the game, not managing the window. These problems become more noticeable/important when you’re making a general-purpose UI framework for non-gaming applications.
I’ve learned the SDL3 has added a new callback-based API to address the same problems (Main callbacks in SDL3), and, it’s not just Windows that uses this kind of API but I guess there are some new web-based platform that also do?
On another note, it looks like you’ve yet to implement dpi scaling so here’s some guidance if/when you get to it. In general to support DPI awareness you’ll want to scale all your UI by a factor of GetDpiForWindow() / 96.0
. Note that DPI can be different for each monitor, and, I like to keep two monitors side-by-side that are at different DPI’s and quickly move a window between them to test that I’m handling the change correctly. (Changing the DPI on a monitor just means changing the Monitor Scale, see Win32DPI and MonitorScale). Next, note that all modern windows applications should be as dpi-aware as possible (PerMonitorV2 if windows is new enough). There are ways you can enable this programmatically but in my experience this always seems to break in subtle ways, the only full-proof way I’ve found is by adding a manifest file so the OS can determine the DPI awareness before jumping into the executable’s entry point. Note that you’ll want to handle the WM_GETDPISCALEDSIZE
and WM_DPICHANGED
. You get the first when DPI is about to change and Windows needs to know the size of the new window, and the later is where you actually change the window size after the DPI change occurs. In the latter you never want to re-calculate/change the size you gave the OS in WM_GETDPISCALEDSIZE
, otherwise, that window size change could place the window on another monitor and change the DPI again, you can end up in an infinite DPI change loop. WM_DPICHANGED
should only ever call SetWindowPos
with the new suggested rect, and, it’s important that you do call SetWindowPos
yourself, DefWindowProc
doesn’t.
P.S. I bring up the points about the callback-based API not to say that your framework should change, just to point out the tradeoffs. I think it’s good to have cross-platform based APIs that sacrifice polish for the sake of simplicity, and maybe that’s the tradeoff you want to make with your library.