Minimum viable declarative GUI in C++
Introducing a “toolkit”
Here is the code of a minimal GUI toolkit for defining declarative UIs:
Well, this plus some extremely routine parts of “ISO/IEC JTC1 SC22 WG21 N 4860 - Programming Languages : C++”, such as… strings… variable declarations… struct definitions… enums… ints… and a couple others. Code so post-modern it circled back to orthodoxy.
So, here is how one uses the C++ language plus this, uh, toolkit to declare a simple item, say, a text label:
Believe me, dear reader, that is actually enough user code to be able to show the following UI:
By user code, I mean that all the “nice” UI work can be done in, say, foo.hpp
:
without requiring any custom header. Some yet-unknown .cpp file will include foo.hpp
, and, no matter what the content of my_ui
is, will execute a nice user interface from it. Anyone will be able to create independent libraries which will render UIs according to whatever platform-specific intricacies there are, but the actual UI code will be entirely independent from anything: peak separation of concerns is achieved.
Advanced UI
The code above can be trivially extended to showing not one, but TWO labels:
The result:
Now, one notices how the visual position of the labels in the UI has the good taste of matching the one in the code: the language provides us with a native primitive for ordering disjoint things, so why not just use it ?
Of course, just putting elements in a vertical row may not be sufficient for every UI on earth.
Let’s declare that our UI embraces horizontality instead, in the simplest way possible that I could find:
And we get:
Nested UI
We may want to imbricate multiple things:
Gives:
We may want to go a bit deeper:
The controls are pointers to simple structs which define a couple of metadatas in the way mentioned in the reflection blogpost.
A nice thing here is that unlike many frameworks, there’s not much memory to leak, as the very structure of C++ structs is used to define the hierarchical UI.
Of course, there has to be a rendering framework somewhere: the nice thing is that the UI code has no dependency on the actual framework. Instead, some separate glue code will transmute our declarative UI definition. Thus, which framework exactly mostly does not matter, for a large amount of potential UIs.
To give an example, the screenshots above were taken with a first prototype of renderer which generates QML / QtQuick code (and runs it).
Trivial UI backends
Here is the exact same UI specification, but rendered with Nuklear instead, which is a fairly different UI paradigm code-wise:
I have spent an hour on it without prior knowledge of the library and did not find how to do splitters and group boxes well with it, but I’m sure there is a way !
Meanwhile… t r e e s.
The binding from this kind of UI specification to Nuklear holds in a ~200 lines (fairly dirty) file.
Here is the core loop, for reference:
Conclusion
Glory to the post-library era and to declarative, struct & enum-based specification !
The only remaining step is to integrate it with score’s internal widget set and there will finally be a way to specify audio and media plug-ins in a way entirely independent from any host app or framework, with zero-cost abstractions.
Here is the example plug-in showcased above, done in two ways:
- First version really only uses zero dependencies to show the sausage-making https://github.com/celtera/avendish/blob/main/examples/Ui.hpp.
- Second version use some helpers macros: https://github.com/celtera/avendish/blob/main/examples/Helpers/Ui.hpp. I don’t find them super convincing, but the nice thing is that the “public API” can be prototyped without issues ; as many distinct versions can exist concurrently. In the end, they pretty much won’t exist in the binary as things can be made constexpr fairly extensively: the whole layout object can be constexpr for instance.
A few small additional things may be useful:
- An API for loading images, for instance to render pretty SVG background à la VCVRack.
- Maybe investigating reactive properties. I have some ideas for those, which may actually fit in these margins :-)
This work can be tried with the avendish library.