In the modern world of computers, just about everything a non-programmer and non-system administrator uses on a computer has a graphical user interface, better known as a GUI. A GUI can be built with a system’s native GUI library, like the Windows API on Windows or X (or its successor, Wayland) on GNU/Linux. Some people enjoy toying around with the low level features provided by these APIs which provide a lot of power to those who understand them. For everyone else (myself included), there are the GUI toolkits.
Earlier, I decided that I would be using the Simple DirectMedia Layer as the windowing library for my OpenGL project in Ada. However, creating windows and other relatively low-level tasks like sensing input is about all that SDL does. Unlike a true GUI toolkit, SDL does not have a function to draw buttons or build menus or even show dialog boxes with pesky error messages. Instead, to draw a button, one needs to first draw a box, write text on top of it and detect when a user clicks inside the box. The same applies to the menu and the dreaded error popups.
If choice of programming language is no barrier, there’s a wide variety of choices for drawing these GUI widgets simply and efficiently. However, I am using Ada, and in Ada, there is a grand total of four significant GUI toolkits available (that I know of): wxWidgets, Agar, GTK+ and Qt.
wxWidgets is a very extensive library of widgets for C++ but with a binding to Ada (that is presently incomplete — see comments). Its appearance is decent, and applications appear fine on most platforms. However, it features very basic OpenGL support, and I’m not aware of a single major project focused on OpenGL that uses wxWidgets.
Meanwhile, Agar is used exclusively for OpenGL applications as its interface is within OpenGL. However, I find the appearance of the interface rather revolting — it might be fine, or may even fit into a game, but for anything else, the overly rectangular and low contrast interface is simply outdated and unacceptable to me.
GTK+, which is equipped with bindings by AdaCore, is like wxWidgets but is far more popular and is used throughout the GNOME desktop environment for applications like GIMP, Inkscape and Nautilus. It, however, is originally written in C which makes the API simpler and easier to understand for those not familiar with C++ (ie. me). Unfortunately, it also does not have reputable OpenGL support, and so I can’t use it either.
Had I come from a background of C++, I would’ve selected Qt as my GUI toolkit of choice. It has the most visually appealing design and integrates smoothly into OpenGL. However, I come from C, not C++. As Qt makes extensive (and from what I’ve heard, quite effective) use of C++ with only C++ documentation and guides which I find impossible to understand, I have to pass on using Qt. A shame, but I really don’t want to have to learn another language (and a rather ugly-looking one for that matter) for the purpose of understanding a library that I won’t even be using in its native form. Qt has at least three different bindings for Ada which are similar, but with licensing differences (so read carefully).
And so, with my pickiness, I’ve exhausted all the existing options. I then considered whether I could port another library into Ada, but decided against it after finding no suitable C GUI library with an appearance I could stand, and so, I’m off to build my own.
There are probably a good number of you right now thinking “you can’t just build another GTK+ or Qt as a simple ‘side project’ — those projects took hundreds of developers years to make!” And that’s true. If I were to rebuild Qt from scratch by myself, Ohloh says I’ll need 917 years. Since modern medicine hasn’t managed to make me immortal yet, I’ll pass on that task and build something on more reasonable scale.
I don’t need the whole of Qt. In fact, I don’t know of a single, well-known independent project that needs all of the features provided by Qt (if you exclude large software collections like KDE). That’s because Qt is designed to satisfy the needs of as many different projects as possible — and some of those projects have very extensive user interfaces. I don’t need something with that much power; I just need something to render a few buttons, checkboxes, dialogs and perhaps some very basic menus without tons of bugs in a manner that doesn’t make my eyes hurt. I just need to meet those requirements and learn a bit of Ada in the process and I’m all set.
The library will revolve around widgets linked together in a multi-dimensional linked list. There will be many types of widgets, all of which are records derived from a base Widget type that is never actually used.
type Widget_Ptr is access Widget;
type Widget is
Parent : Widget_Ptr;
Child : Widget_Ptr;
Prev : Widget_Ptr;
Next : Widget_Ptr;
The confusing part for me is now figuring out how to let the Parent or Child be, for example, a Button_Widget, without impeding access to the additional fields like Width and Height. After reading a bit more on polymorphism in Ada and contemplating the workings of object-oriented design for half an hour, I found that I could have an overloaded procedure to accept each of the various types. Or so I think — I haven’t tested it yet, but that seems to be the case.
Width : Integer;
Height : Integer;
procedure Draw_Widget (Widget_Object : Button_Widget);
In addition, Widget must be redefined with the tagged label to declare that it can be expanded. I also need to redefine Widget_Ptr to reference Widget’Class, the classwide type that also Button_Widget.
type Widget_Ptr is access Widget'Class;
type Widget is tagged
Parent : Widget_Ptr;
Child : Widget_Ptr;
Prev : Widget_Ptr;
Next : Widget_Ptr;
The above code compiles successfully, but as I’ve said, I haven’t actually tried to use it yet. Hopefully it’ll do what I think it’ll do and just work. If not, I’ll be back at it next time…
In any case, the library will render widgets with OpenGL and will function exclusively within an OpenGL context. It will have a display procedure which will be called towards the end of an OpenGL application’s main loop to render the interface on top of the existing OpenGL display.
-- do OpenGL drawing here
Render_UI; -- draw the UI
-- swap buffers, etc...
The Render_UI procedure would start at a “root” widget which would then serve as the starting point for traversing the tree of widgets that follow. The “root” widget should be set before entering the main loop and can be within the main loop to perhaps switch to a different interface without destroying the existing one. As a result, the linked list is effectively ordered by z-index with the widgets at the front of the list being rendered first and therefore appear furthest in back. A couple of procedures, To_Front and To_Back would be available to change the z-index as necessary (ie. a Window_Widget towards the back is clicked, causing it to move to the front).
In addition, certain widgets, like buttons, would also need event handlers to be useful. In Ada, access types can reference functions and procedures in addition to variables. Each type of handler will need its own type to handle the appropriate arguments.
type State_Change_Handler is access procedure (New_State : Boolean); -- for checkboxes
type Text_Change_Handler is access procedure (New_Text : Unbounded_String); -- for textboxes
-- and so on...
And now I’m off to learn object-oriented programming in Ada — or actually, maybe even in general as it’s been years since I’ve touched the topic. Hopefully, I’ll be ready to start implementing some of these plans by next time. Until then, it’s time for me to RTFM — the Ada Reference Manual, that is. 😉
This is part of a series on programming in the Ada language, a mission critical programming language that I am attempting to learn and become proficient in. As a consequence, any code you see on this page should be taken with a grain of salt — maybe even several grains to be safe. Well, you get the point.