We remember that the current state of building the terminal application was creating the user interface. I tried to use HTML to do the formatting of the text which turned out to be way too slow.
So I had to find a different approach as I wanted to stick with QML and .Net (for the already mentioned reasons)
In QML there are a few alternatives for getting a formatted text to the screen. We already had a look at the HTML approach.
Another approach is to create a QQuickPaintedItem. You can imagine that control to be a drawing surface inside your QML layout that then can use a QPainter and its drawing API to draw whatever it wants.
This works by deriving a class from QQuickPaintedItem, implement the paint method and use the passed QPainter instance to draw.
The paint method is called whenever the content has to be redrawn.
This new class then has to be registered as a QuickItem with the QmlEngine and can then be used like any other QuickItem in your layout.
Gladly I was already involved with that project in the past so I understand enough of its inner workings to create QQuickPaintedItem support.
There was already a GitHub issue open asking for exactly that functionality so I started tinkering with the Qml.Net implementation to find a way to support this.
Interlude: QML.Net inner workings
QML.Net makes you use your .Net types in Qml. It does this by using reflection (at least currently) to analyze your .Net type and sending this information to a QObject derived type on the C++ side that dynamically mirrors your types properties and methods into the Qt world.
This is done by some “magic” that includes a very deep knowledge about the inner workings of Qts meta type system.
So effectively every .Net type you are using in the Qml world has such a representative type in the Qml world.
QML.Net takes care of all this and even is able to generate the proper Qt interfaces for .Net patterns (MVVM) if you configure it to do so.
This mechanism only works for “information” objects as the dynamic type on C++ side is derived from QObject. Visual elements are derived from QQuickItem (which derives from QObject but adds all the behavior and state needed for visual elements)
In order to use the QQuickPaintedItem functionality from .Net we have to have a C++ type that derives from QQuickPaintedItem and calls back to .Net whenever the paint() method is called so that the .Net class can control the actual drawing of the content.
Additionally to that we also need to mirror any additional properties or methods the .Net side has created to QML as you would expect to access those just as if you would create a C++ class derived from QQuickPaintedItem.
So we need two things:
- a QQuickPaintedItem based C++ type that is able to be dynamically created per custom PaintedItem you create on the .Net side (at runtime)
- Use the same mechanism for mirroring properties and methods (and a bunch of other stuff like custom signals) like “information” types use to get that functionality that every property or method you add to your .Net based PaintedItem gets mirrored into QML
So - after some failures and discussion with Qml.Net contributors - I came up with this solution (still a PR at the time of writing):
In C++ there is (as for the normal types) a representation type that is derived from QQuickPaintedItem. By using the same template trick (like for normal types) we can define a number of slots for such dynamic types.
Every type knows what .Net type it is linked to so that we can route the paint() callback to the exact class and instance.
The only thing we are missing now is the mirroring of all additional properties and methods.
For this we use the .Net type (that is derived from QmlNetPaintedItem) and extract its metadata just like for normal .Net types. The difference is that we stop at QmlNetPaintedItem (we don’t want to mirror that type, only the custom additions).
On the C++ side we build up the Qt metainformation in a way so that it defines the dynamic part of the type using the information of the additions and then - instead of declaring that the base type is QObject - point the base type to the QQuickPaintedItem representative it belongs to.
All this happens in the meta system so the concrete C++ type is not derived from QmlNetPaintedItem. You can imagine that as a virtual connection.
We can do that because every interaction with such an instance is happening via the meta system where we can route the calls at runtime to the right place.
This way for Qt it looks like the QQuickPaintedItem has a sub class that contains all the additions and therefore you can use the representative just as if you would have created that type in C++. The whole QML.Net infrastucture makes sure that the .Net part and the Qml part are linked correctly.
This approach is much much better from a performance perspective. Even despite the fact that all the calls to QPainter (so basically every draw command) is a P/Invoke call.
Of course using a different UI technology might reveal a better performance but I’m fine with the result and will continue using .Net and QML.
When I started with that terminal project I never would have expected that much effort and I’m still on my way to have feature parity with the simplest of all terminals (read: the core feature “terminal” works) and did not even start with all the things I want to add and that were the reason for starting this project in the first place.
So expect much more blog food incoming.