In version 1.0, the canvas view is implemented as a customized QGraphicsScene/View. It has worked fairly well (surprisingly well, even) but Qt Quick has some irresistible advantages:
- OpenGL acceleration
- Declarative UI markup language! (QML)
- Much easier to port to mobile platforms
Since I had never used QML before, I decided to start by porting the playback dialog over first to get some practice. I chose this part because the dialog is fairly self contained and could therefore be rewritten without touching much else (yay, low coupling!)
The playback dialog
I started by separating the application logic from the dialog class into a PlaybackController class. The idea was to dump the old class entirely and open the dialog directly from QML. Unfortunately, it wasn't that easy. While you can open dialogs from QML, the behavior gets really odd when you open another dialog from within a dialog.
First, we have the playback window (a tool dialog of the main window.) From it, we can open the video export dialog. And from that, a file selector. One might argue that that's too many nested dialogs, but this works fine with traditional Qt widgets, but breaks in strange ways when using QML. The exporter dialog would open as a sheet to the parent dialog on OS X (there doesn't seem to be any way to control this.) Next, opening and closing the file dialog detaches the sheet and its Ok and Cancel buttons stop working.
Qt Quick is still quite new, so I have no doubt these bugs will be worked out eventually, but for now I had to fall back to using it in combination with the tried-and-true QWidget based dialogs. So, I kept PlaybackDialog, but gutted out the widgets and embedded the QQuickView in their place. Qt offers two ways of doing this: QWidget::createWindowContainer and QQuickWidget. QQuickWidget renders the scene to an offscreen buffer first and can be stacked like any ordinary widget, but carries a performance penalty. Since I don't need to stack anything on top of the view, I chose to use createWindowContainer.
With all the UI-agnostic code moved to PlaybackController, what was left to PlaybackDialog are the methods that open more dialogs. This means there is still some work ahead if/when I want convert the dialog to pure QML, but in the meantime I get to keep all the existing subdialog code. This is an OK tradeoff, since the subdialogs have very basic form layouts that wouldn't really gain much benefit from QML anyway.
|Thew Qt Quick based playback dialog|
So, what was gained from the migration to QML?
First, let's take a look at the old architecture. We had the PlaybackDialog class, its form based UI designed in Qt Designer and UI and application logic all tangled up in playbackdialog.cpp.
The first task was to separate out all the controlling logic (playback control, timers, etc.) into an UI-agnostic class. This class provides an API for the UI layer to use.
Previously, there was explicit code to update the progress bar (and other widgets) when the recording was advanced. In the new architecture, stepping to the next frame simply updates a progress counter and the UI element bound to the value notices the change (through Qt's property change notifications) and updates itself. This is a much cleaner separation of layers, and perhaps the way it should have been done in the first place, but writing the UI in QML really forces you to adopt this approach.
QML also makes it possible to do things that would have been difficult to do with widgets. For example, the old dialog had a checkbox for switching to a "presentation" mode with a reduced size playback window. In the new implementation, the checkbox (which really was an inappropriate control for this task) is gone, but the small player window is still there. The window is now what web developers call "responsive". Rather than needing a button, it now automatically switches layouts when resized. This would have been possible to implement with widgets too, but in QML, the switching logic is literally just three lines of code!
Migrating the playback dialog to QML actually shortened the codebase by about 600 lines and (in my opinion) made the C++ parts a little easier to understand as well, so I'm really looking forward to cleaning up the canvas too!
Coming up next
Part 2 of this post series is about porting the canvas from QGraphicsScene to Qt Quick. This will involve writing custom QQuickItems to render the canvas pixel data, annotations, tool previews and other items. Tablet input must also be handled somehow.