|Programming||By Shamus||Apr 15, 2012||117 comments|
You might remember that I wrote a program for laying out comic strips. I stopped using it when I stopped working on Stolen Pixels to become an author. I think about Comic Press now and again, feeling vaguely guilty that I’ve got this useful chunk of software sitting on my hard drive, basically going to waste. I should clean it up and release it to the public. Or do some tipjar-based development on it. Or something.
And then I remember that Comic Press is in this oddball limbo state. It was written in Visual Studio 6, which came out in 1998. It uses a ton of non-portable Windows code. I can’t even compile it now that I’ve migrated to Microsoft Visual C++ 2010 Express. I need to update all of the dialog and menu code to get it running. But if I’m going to do that, I should fix this interface to make it a little more portable. But if I’m going to do that…
You might remember that a couple of years ago I wrote a rant on how much of a monumental pain in the ass it is to use someone else’s library, particularly in C++. I wrote that when I was looking for a GUI system to work with OpenGL. I just revisited the issue this week, and re-familiarized myself with all the annoyances I’d forgotten.
A GUI system is what enables you to add standard controls to your program. Buttons, menus, checkboxes, file open dialogs, scrollbars, edit windows, and so on. See, in Windows (or Linux, MacOS, etc.) the operating system can do all of that for you. With a few lines of code I can make a button…
I can tell the system, “stick a button right here”. Then Windows (the operating system) will handle everything else. It will draw the button, making sure it matches the theme used by this particular computer. It will make the button look “pressed” or “disabled” at the proper times. It handles hotkey interactions. It tracks mouse movements and clicks to see where the user is clicking, and it will send me a little message if the user ever gets around to activating the button so I can take the appropriate action. I do not hold up the Windows API as some great work of technological engineering. It’s old and awkward and clunky and not terribly efficient. But it’s there, and it saves all of us Windows programmers from needing to write our interface code from scratch.
The problem is that we lose all of those features once we’re talking about a window that renders 3D stuff. For example, if I’m rendering a 3D scene in OpenGL:
Basically, you can’t do this:
I could make a Windows-style window to sit in front of the 3D window, but that’s not the same thing. It’s the difference between using the chat window in the corner of your World of Warcraft screen and just having (say) a Notepad window positioned over that same area of the screen. It doesn’t work.
So once you’re doing the 3D graphics thing, you’re stuck making interfaces yourself. This is a lot of really fiddly work. Most people don’t even realize how crazy complicated a scrollbar really is. There’s buttons at the top and bottom for moving up and down a “line” at a time. There’s the scroll area, which should let the user jump to the given point in whatever thing they’re viewing. Then there’s the shuttle control itself, which changes size based on the ratio of the total document and the potion that is immediately visible. There’s interactions between the document and the scrollbar as the document changes size. Using the mousewheel on the document should move the scrollbar, and using the arrow keys on the scrollbar should move the document. This adds up to days of work, just for a scrollbar. Add in the time you’ll need for text windows, button bars, progress bars, sliders, spinner controls, and all the other things we take for granted, and you have months of work.
For an interface!
A lot of this probably explains why game interfaces are so primitive.
I don’t imagine we’ll be seeing a text box with a scroll bar in Minecraft anytime soon. I’m betting Notch had to program his interface from scratch. If I made an interface by hand, this is exactly what it would look like: Buttons and sliders. Those are far easier to implement than other types of controls. Note how the difficulty selector is a button with four states: Peaceful, Easy, Normal, Hard. In a full-featured windowed environment, usability standards would normally call for a drop-down box in this context.
Over the years, a lot of enterprising developers have taken a stab at solving this problem. They do this by making a library, which is a big ol’ lump of code that you can add to your project. Some of the notable ones are:
Some of these were abandoned before they were finished. Some are poorly documented. A lot of them are just hard to use. Some are stuck looking like Windows 95. Some are difficult to compile, or have challenging dependencies.
Ideally, a library should be as simple as possible. If you make a library to handle the physics in a game environment, you shouldn’t include a bunch of crap for generating sound effects. Yes, the programmer using your library will probably need sound effects, but they might already have a sound system. That extra sound code stuff will be all the more stuff that they have to compile. It will create more dependencies and make the library that much harder to learn, integrate, and debug.
OpenGL is a perfectly focused library. It does rendering, and only rendering. It doesn’t know how to load images. It doesn’t talk to your file system for saving and loading data. It has no physics, no particle engine, no support for 3D models. No sound, no joystick support, no special effects. It’s concerned with the low-level, nitty-gritty details of drawing polygons. If you want to load in a texture map, you load in the image (JPG, PNG, BMP, TIFF, etc) yourself (you’ll need another library for doing that) and just feed OpenGL the raw image data. It doesn’t concern itself with where images come from, where they reside on disk, or what they’re named.
I said it’s a perfectly focused library. I did not say it was a perfect library. It concentrates on doing one thing, and doing it well. If you need more you can get other libraries to do other things. If you want a lot more help you can use one of the many game engines built on top of OpenGL. But the core is there, uncluttered, if that’s what you need.
It’s fine for me to sit here and make broad statements about keeping libraries simple, but in the real world this kind of platonic ideal is actually really hard to achieve. The problem here is that you need a lot of stuff to make a proper GUI. You need fonts. (You can’t have an interface without fonts!) You need to be able to load images. (Because those interface buttons have to be made of something.) You need to process keyboard and mouse input. (Otherwise, what’s the point?) It needs access to the rendering systems. (So that it can draw its controls.) This means your “interface” library needs hooks into the filesystem, rendering, an image loader, peripheral I/O, and a font loader.
This is a nightmare. A lot of those things are messy, non-portable systems, which means they will vary from one operating system to the next. It means the library will need to include lots of other libraries, which will lead back to my original rant.
It’s a tough problem, which is why I keep walking away from it every time I revisit Comic Press.