Tools Development and The Need For GUI Code
As some of the "foundational" pieces of code have been coming together, I've realized that I need to get some tools created to assist me in content creation. This would be for things from asset conversion to tilemap editors.
Asset conversion tools (e.g. going from BMP/PCX to a game-engine specific format optimized for faster loading) can really just be command-line driven which makes it simple enough to create. For more involved things such as a tilemap editor, this requires some GUI code.
I made a few tools in straight DOS using QBasic back in 1998/1999 or so with my own homebrew GUI code that was pretty terrible as I recall. But it was my very first experience writing such code (and only a few years after I first learned to program), so perhaps I should not be too hard on myself. I actually want to add a "projects" section to this website in the near future where I can post that old code just for fun, but currently I don't have it in a presentable format to share for this post.
Very soon after, I moved to Visual Basic (first v4, and then v6) and of course, that made writing such tools infinitely easier. I was actually pretty happy with the tilemap editor I wrote (from scratch entirely in a weekend):
For my current DOS projects, I once again need to revisit GUI code 20 years later. There are some libraries I could use of course, but I am going the "Do It Yourself" route (a.k.a. the "Not Invented Here Syndrome" route) for fun and for the (re-)learning experience. With that in mind, I decided I wanted to pursue an Immediate Mode GUI implementation. Probably the most well known implementation of this these days is the Dear ImGui library.
I began reading up on it in more detail, having not implemented such a system before in any project I'd worked on. This video in particular is a great introduction to it (and I believe, the original video on it) if you're not too familiar with why this method of implementing a GUI might be useful and where/when. As well, I found this tutorial series to be quite useful.
Eventually I did get something working myself:
It's far from perfect, but I'm happy with how it's coming along.
while not KeyState[KeyEsc] do begin
event := PollEvents;
if event <> nil then UIProcessEvent(event);
Cls(9);
UIBeginFrame;
UIBeginMenuBar;
if UIBeginMenu('File', 100) then begin
UIMenuItem('New');
UIMenuItem('Open');
UIMenuItem('Save');
UIMenuItem('Save As');
UIMenuItem('Quit');
UIEndMenu;
end;
if UIBeginMenu('Edit', 100) then begin
UIMenuItem('Cut');
UIMenuItem('Copy');
UIMenuItem('Past');
UIMenuItem('Clear');
end;
if UIBeginMenu('Help', 100) then begin
UIMenuItem('About');
end;
UIEndMenuBar;
UIBeginWindow(50, 30, 250, 180, 'window!');
if UIButton('button 1') then
clicked := true;
if UIButton('button 2') then
break;
UISetNextID('vertSlider');
UIVertScrollBar('', 20, 0, 2, value);
UIHorizScrollBar('slider!', 0, 0, 200, value2);
UICheckBox('checkbox', checked);
UIListBox('list!', 0, 3, list, listScrollY, listSelection);
if UIRadioButton('one', (radio = 1)) then radio := 1;
if UIRadioButton('two', (radio = 2)) then radio := 2;
if UIRadioButton('three', (radio = 3)) then radio := 3;
UIEndWindow;
UIEndFrame;
DrawMouseCursor;
WaitForVsync;
Flip(BackbufferLayer);
end;
Some stuff doesn't work completely yet (such as the menus). As well, I'm missing two main widget types: textboxes and dropdown lists. Actually, I'm not expecting textbox widgets to be too difficult to implement. In fact, the input events system that I originally added to libDGL was originally added purely because I knew I was going to need such a thing to more easily implement such a textbox widget in the future!
However, I am relatively happy that things like automatic layout/positioning mostly works well, as well as automatic
widget ID generation. Well, except that, as you can see above, there is an escape hatch for this in the form of
UISetNextID
that can be used when/if needed.
The idea with pretty much all of the widgets is that the widget function (e.g. UIButton
, UICheckBox
, etc) takes
simple properties like a label string and maybe a couple other common style- or functionality-related properties as
well as state variable(s) which are passed by pointer/reference to the widget. If the widget state changes, the
variable(s) are updated immediately with the new state and the widget function will return a boolean to indicate that
the state was changed. This boolean indicating that the "state was changed" is a bit context-dependant. For example,
for a UIButton
, it doesn't take in any explicit state variable itself (that is managed by the UI system in the
form of widget IDs being tracked for what widget the mouse is currently hovering over, etc). But, the UIButton
still returns true/false to indicate if it was clicked or not. For a UICheckBox
which does require an explicit state
variable to be passed to it, it returns true/false to indicate that that state variable was changed in response to a
user action. This allows the application code to easily respond to the most common user interaction events for widgets
with just simple if
logic. Which is really, really nice actually! The previously linked video on immediate mode GUIs
explains this idea really well and why it's nice for programs which do real-time rendering.
The key thing for me with this GUI code is that I do not want to create a totally generic UI framework. I only need to create something that will allow me to create some basic tools easily enough. Maybe eventually I'll want to add support for things like custom themes so that this could be used directly in an actual game project while matching the game's look and feel. Or, later on I might need more widget types. The list goes on, but I do not want to get lost in a never ending quest to get a "perfect" UI framework up and running, because well, it really would be a never ending quest and frankly, I don't need such a thing!
Right now I think I'm still going a bit too far with this by keeping support for things like tabbing between widgets. Will I actually use this feature in tools I create for myself? That remains to be seen (I suspect not, but you never know).
Some of the widgets leak implementation concerns a bit here and there which I'm not happy with. I've decided to just roll with it for now and hope that inspiration will kick in later and I'll be able to clean up these warts later on.
For example, the UIListBox
widget requires two UI state variables to be provided to it. One of them,
listSelection
, is obviously needed (it holds the user's currently selection). The other one, listScrollY
, holds
the list's internal scrollbar scroll position and is kind of silly, but I didn't have any clever ideas for how to
efficiently and automatically manage state of internal child widgets (the UIListBox
widget internally uses a
UIVertScrollBar
widget).
Another example of this is in the menu code (which is mostly unimplemented as I write this). UIBeginMenu
takes a
second parameter which is the horizontal width of the menu to be created. This is kind of unfortunate, but I'm not
exactly sure as of yet how to have this automatically figured out in a way that doesn't involve some kind of
deferred rendering approach. I'm currently against the deferred rendering approach as I don't want to have to keep
that kind of state around in memory somewhere... but it might be the best approach as it also solves another problem
with my current approach which is that overlapping sub-menus would not render correctly. I might leave deferred
rendering as a "v2.0" feature, heh.
Anyway, I'm hoping to have the UI stuff finalized in a workable state (even if it's not perfect) within the next week which will allow me to move onto creating tools for myself. Additionally, this UI code will need to be ported back to C so I can use it with libDGL eventually. I've been doing it with Turbo Pascal mainly for the reasons mentioned in my last post ... I find Turbo Pascal to be quite a bit faster for prototyping and experimentation.