Designing a (Simple) GUI Toolkit in Ada with OpenGL

By | August 15, 2013

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.

Existing Options

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.

Objectives

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 Design

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;
type Widget_Ptr is access Widget;
type Widget is
   record
      Parent : Widget_Ptr;
      Child : Widget_Ptr;
      Prev : Widget_Ptr;
      Next : Widget_Ptr;
   end record;

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.

type Button_Widget is new Widget with
   record
      Width : Integer;
      Height : Integer;
   end record;
procedure Draw_Widget (Widget_Object : Widget);
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;
type Widget_Ptr is access Widget'Class;
type Widget is tagged
   record
      Parent : Widget_Ptr;
      Child : Widget_Ptr;
      Prev : Widget_Ptr;
      Next : Widget_Ptr;
   end record;

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.

loop
   -- ..
   --  do OpenGL drawing here
   -- ..
   Render_UI; -- draw the UI
   -- swap buffers, etc...
end loop;

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 Click_Handler is access procedure; -- for buttons
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.

7 thoughts on “Designing a (Simple) GUI Toolkit in Ada with OpenGL

  1. Luke A. Guest

    I wrote wxAda and its not finished as I ran into problems. I and many others would say that wxWidgets is far more popular than GTK+.

    You also didn’t mention there are bindings to the UI toolkits you mentioned, bar wx. You also ding seem to know that Lumen is doing what you are already and has window management already. Go to #Ada on Freenode.net.

    Reply
    1. North Post author

      Hm…there really needs to be a place to find Ada libraries more efficiently. It’s not listed in either the AdaIC list or on the AdaPower list of GUI toolkits or even on the Ada Wikibook page of GUI toolkits. I took a look at it and its goals are similar, but perhaps a bit beyond what I intend to do. I’m using SDL (with the AdaSDL binding) to handle the windowing and everything else SDL normally does, so I’m really looking for a pure OpenGL library that isn’t tied to any particular windowing toolkit. I actually eliminated several of the options earlier in choosing a binding that allows use of OpenGL in Ada (where I chose AdaSDL), so of the items on the list, only libagar was a real contender at this point and included the others just for completeness and to hint at what design influences they may have on my library.

      I based my assumption that GTK+ is more popular on what programs I use. On Windows (using Debian is a bit unfair since I have GNOME), I have AbiWord, GIMP, Inkscape, Ekiga and Pidgin that use GTK+ while only having Code::Blocks and FileZilla that use wxWidgets (based on the Wikipedia categories for GTK+ and wxWidgets). (I’d actually never even heard of wxWidgets until installing Code::Blocks) That, or it’s just because I never really worked with C++.

      Also, you’re right that I completely overlooked the links to the Ada bindings! I’ll add those in right now. Thanks for the note!

      Reply
      1. jns

        > “Hm…there really needs to be a place to find Ada libraries more efficiently.” —> That and packages for distro’s (which might take care of finding them too)

        Reply
  2. Me Write Code

    Hi,

    If in the end you still intend to write your own GUI toolkit, I’ve seen it suggested on comp.lang.ada to use polymorphism instead of function pointers for event handlers. Something along the line of:

    type Button_Widget is new Widget with private;

    procedure On_Click_Event( Button : in out Button_Widget; States : Mouse_States );
    procedure On_Focus_Event( Button : in out Button_Widget'Class );

    The primitive operations would have default implementations that probably would do nothing. Then you could derive your own:

    type My_Custom_Button is new Button_Widget with private;

    -- Change behavior to do something when the button is clicked.
    overriding  procedure On_Click_Event
       ( Button : in out My_Custom_Button;
          States : Mouse_States );

    I’ve never tried this myself since I don’t normally write GUI apps, but maybe it’s something to consider. Maybe another variation of the idea could be to have widgets take in a pointer to an event handling object:

    type Button_Widget is new Widget with private;

    procedure Install_Handler
       ( Button : in out Button_Widget;
          Handler : Button_Events_Handler_Ptr );

    type Button_Events_Handler is tagged limited private;

    procedure On_Click_Event
       ( Handler : in out Button_Events_Handler;
         Button : in out Button_Widget'Class;
         State : Mouse_State );

    procedure On_Get_Focus_Event
       ( Handler : in out Button_Events_Handler;
          Button : in out Button_Widget'Class;
          State : Mouse_State );

    Then you would derive from Button_Events_Handler to make your customizations:

    type My_Button_Events_Handler is new Button_Events_Handler with private;

    overriding procedure On_Click_Event
       ( Handler : in out My_Button_Event_Handler;
         Button : in out Button_Widget'Class;
         State : Mouse_State );

    Then later do:

    B : Button_Widget;
    Handler : My_Button_Events_Handler;

    Install_Handler( B, Handler'Access );
    Reply
    1. North Post author

      Thanks for the great ideas. It took a while to figure out, but they do make sense now. It seems a goal in Ada is to avoid pointers to anything substantial whenever possible which I assume helps prevent a class of errors involving pointers. Which is great, because I never did like pointers. :)

      If nothing else, I’ll certainly at least gain a better understanding of the language after writing the GUI library. Thanks for your help and the time you put into writing that comment!

      Reply
  3. Zerte

    If you want to get an inspiration, look at how GWindows works.
    You have both the handler-with-access way and the polymorphism way, without even the word “handler” appearing:

    type Browser_window_type is new GWindows.Windows.Window_Type with record … end record;
    overriding procedure On_Create (Window : in out Browser_window_type);
    overriding procedure On_Focus (Window : in out Browser_window_type);
    procedure New_Tab(Window : in out Browser_window_type);
    procedure Next_tab(Window : in out Browser_window_type);
    procedure Close_tab(Window : in out Browser_window_type);
    procedure Go_on_URL(Window : in out Browser_window_type);
    overriding procedure On_Menu_Select (Window : in out Browser_window_type; Item: in Integer);
    overriding procedure On_Close (Window : in out Browser_window_type; Can_Close : out Boolean);

    Reply
    1. North Post author

      Thanks for the tip! The event handlers will probably end up being among the last of the parts I implement, so I still have plenty of time to sort things out carefully.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *