Writing a custom QStyle
It has been a long time that I wanted to write a total custom appearance for Qt applications. My experience with Qt’s CSS stylesheet was not very good, and I stumbled upon Phantom Style, by Andrew Richards, a custom-made Qt style that improves from Qt’s Fusion Style. Its look and performance is pretty good. So I decided to write my own, in a more modern fashion. And it’ll be the occasion to add dynamic theming. Here is a picture of what it looks like with the default colors, on Windows on a standard DPI screen:
What we’ll be handling, apart for (obviously) drawing widgets on screen:
- Animation system
- Dynamic Theming system, from
.jsonfiles, with hot-reload.
- Dynamic Pixmap Coloring, to ensure icons stay coherent with the theme colors.
- Out-of-widget-bounds Focus Border, à la macOS.
- Drop Shadows (coming soon).
QWidget and QPainter
What is QWidget?
QWidget is the class that represents a element on the Graphical User interface. It has thus a visual representation on the screen, and receives events regularly to allow it to update its state. It must be re-drawn on the screen each time it receives an event that suggests a modification of its appearance. For instance, a button will repaint itself when it is pressed by the mouse pointer.
How does QWidget draw itself on the screen?
The drawing of a
QWidget is done when the method
void paintEvent(QPaintEvent* evt) is called. Since this method is
virtual, one’s can override it to customize the painting.
To draw, a
QPainter object is needed. This class has the necessary API to draw anything: rectangles, lines, text, images, paths. The common pattern to paint a
QWidget is the following:
Note that the painting is made by the CPU, with
QPainter’s Raster backend. This has potential implications on performance.
QStyle class is responsible for handling the look and feel of a Qt application. It provides methods to get sizing information, animation durations,… and last but not least: methods to paint widgets.
For each OS,
QStyle has been subclassed and the look and feel of the native widgets have been reproduced. You’ll find
QMacStyle, … and so on. Unfortunately, these class are part of Qt’s private API. If we want a custom look and feel, we have to subclass it too.
Note that there is one
QStyle instance running at a time, which is set on the application with the static method
QApplication::setStyle(QStyle *style). Think of it like a singleton that is accessible from all the widgets.
Why not just using a CSS stylesheet?
Actually, when you use a CSS stylsheet (also known as QSS) on your Qt application, a dedicated
QStyle class is automatically created by the
QApplication to handle the painting based on the content of the CSS file:
QStylesheetStyle. It is a quick way to customize the style of the application.
However this has some drawbacks:
- Performance is not very good.
- Some drawing is wrong (strokes and border-radiuses, for instance).
- Qt uses a subset of CSS2, which is pretty old and limited. Important mising features are animations, shadows, and any kind of advanced graphical effect.
How does QStyle work?
In QWidget’s paintEvent method
QWidget needs to be repainted, its method
paintEvent() will be called by Qt’s internal
paintEvent() method will get the
QStyle that is set on the
QWidget, then use it to draw the widget.
QStyle is a singleton-like object, and does not know the
QWidget, it needs information about the widget’s state and features. This information is contained by the base class
QStyleOption, which is subclassed by all the different widgets. Every
widget has its corresponding
For example, a
QPushButton will draw itself like this (basically):
QStyle relies on
const methods: they don’t modify the input, apart from the
QPainter. They use their parameters in read-only mode, and only call internal draw primitives.
QWidget is identified by an
enum value, and its current state by a
QStyleOption, so the
QStyle::drawControl wouldn’t even need to have the
QWidget as parameter. It is just there as a way of advanced customizing.
QStyle’s code has been properly separated between
const methods to draw and
const methods to get geometry to draw at.
We’ll see the internal workings of a
QStyle in the next part.
Methods to implement
QCommonStyle to avoid handling generic behaviors and only provide necessary code.
Qt divides widgets' visual appearance into smaller interactive parts: subcontrols. These interactive parts are themselves divided into smaller parts: subelements.
QStyle provides primitive dedicated methods to draw these elements, instead of a huge methods that would draw the complete widget. These smaller parts are identified by the following enums. It is not clear how they are classified, though.
enums that define the parts to draw are:
PrimitiveElement: Basic elements used by multiple widgets. Exemple:
PE_IndicatorCheckBoxis the check mark of a
QCheckBoxbut is also used for checkable items in a
ControlElement: A part of a specific widget. Example:
CE_PushButtonBevelis the part that you click on a
SubElement: A sub-part of a specific widget. Example:
SE_PushButtonContentsis the foreground (text+icon) of a
ComplexControl: Widgets composed by other interactive elements (but not widgets). Example:
CC_SpinBox, which corresponds to
QSpinBoxhas a text field and up/down buttons.
SubControl: A widget that is part of a
We need to implement these virtual methods, to handle the possible values of the aforementioned enums (Note: the
p mean that it takes a
const QPainter* and a
const QWidget* as parameters):
||Draws a basic, unit, element (e.g. background of a button).|
Not only we need to draw the widgets, but we also need to provide size and position information to draw them. Some enums help us identify which part to size.
PixelMetric: Used to calculate sizes for the contents of various widgets.
ContentsType: Used to calculate sizes for the contents of various widgets.
enums are used by these methods:
||Gives the size of a part of a widget like margins, padding, icon sizes, etc.|
||Gives the coordinates and size where to draw an inner interactive part of a complex
||Gives the coordinates and size where to draw a part of a
More general-purpose enums and methods:
StandardPixmap: General-purpose icons (success, warning, error, etc.) and images.
StyleHint: Identifies a size, a mode or anything else, that is used to customize the behavior and does not fit anywhere else.
||Returns which sub-widget has been hit by the mouse.|
||Provides specific values (such as border radius).
||Used to give custom icons to widgets, dialogs, etc.|
QPalette, its built-in theming object. Yet, this class is not powerful enough to handle proper modern theming, like we see with current CSS frameworks.
We can create a
Theme class to replace
QPalette, that would contain all basic values like colors, sizes, animation durations, etc.
Theme object can be created from JSON (or any data-oriented text files). JSON is conveniant because Qt has everything we need to parse it (
QJsonObject…). Here is an example:
Note that you have to write parsers to create Qt objects like
QSize, etc. They won’t be all shown here, as they’re pretty straightforward to write, and will depend on your own design and data specifications. I had to write functions like
std::optional<QColor> tryGetColorFromHexaString(QString const& str), as instance:
QStyle can have a current
Theme that it will use to paint. It even allows to change theme at run time, and having everything re-draw.
QStyle only gets information about the widget’s state with
QStyleOption, it does not have any notion of time, which is necessary for animations. We need a way to store this state.
Qt’s own private implementation
Qt’s sources show us that the workaround they used is to keeps a HashMap in the
QStyle, associating a
QObject* to a dedicated object used for handling its animation:
QStyleAnimation. It can animates any
QObject and not only its subclass
When the animation is running and a value (e.g. a
QColor) has been updated, it needs a way to trigger a visual update of the
QWidget. This is done by sending an event synchronously to the
QStyleAnimation is not part of Qt’s public API. We’ll follow the same mechanism by re-making this system from scratch for our own
This is what our system needs to do:
Registering all newly created
QWidgetand their associated
WidgetAnimatorin the HashMap. We’ll do this lazily, only as needed, when an animation is requested.
Triggering animations when the
QWidget’s state changes. We’ll do this lazily, directly in the paint methods.
Providing animated (i.e. interpolated) values that our
QStylewill use to paint the
WidgetAnimator is a class that stores all the animations set on a
QWidget. For the animations, we have two options:
- Create our own implementation.
- Rely on the already-existing Qt implementation that is
The second option might be not the best, performance-wise, but it’s sufficient. Consequently, an animation is handled by the class
WidgetAnimation<T> that is a typed wrapper over
QVariantAnimation. Typing is useful so we won’t have to write the conversion code lines every time.
WidgetAnimation<T> will trigger
a new repaint for the
QWidget every time the animation value has changed.
We need a class that creates
WidgetAnimators when needed:
To animate a property, we have two options:
- Rely on an
ForegroundColor, etc. and we would store corresponding animations into an hash map.
- Directly add the methods in the class.
We chose the second option as it allow us to have typed methods. A
MACRO (not shown here) may help, because the code is the same for all properties.
This method is called at every paint, i.e. for every frame that needs to be drawn.
- If the target value changes, it restarts the animation, with the current one as starting value.
- If the target value doesn’t change, it returns the current interpolated value.
Out-of-widget-bounds focus border
As you may already known, it is only possible to drawn within the
QWidget’s bounds. The
QPainter is clipped to its limit, thus can’t drawn outside of the
If we want a focus border that draws outside the limits of the
QWidget, we can simply use Qt’s
QFocusBorder. Before a
QWidget draws itself, the method QStyle::polish(QWidget* w)
is called, allowing us to install any kind of behavior on the widget. We use this to instanciate aQFocusBorder` on the widgets we want to have this macOS-like focus style.
This class will call
QStyle::drawControl(CE_FocusFrame, styleOpption, widget) to draw itself. We can then check its parent
QWidget’s type to determine the
QRect of the focus border. We need to do this for every type that has a
Cheatsheet for QWidgets and their QStyle enum values
Each QWidget has its dedicated
enum values. Here is a guide to help you with this mess.
QTreeView, QListView, QTableView
QProxyStyle is a way to modify the behavior of a
QStyle by dynamically overriding painting or other specific style behavior. Instead of subclassing a
QStyle-derived class, you need a class that inherits
QStyle checks if it has a proxy before each method call, and calls it if so. However, as said in the documentation, there is no guarantee that
QStyle::proxy() will be called by the
QStyle you override. User-defined or system-controlled styles may ignore their proxy.
Currently, my QStyle doesn’t support the use of a proxy style.
To use a
QStyle on a
QApplication, it can be done like this:
It was a long and tedious task, but now I have more control over what is displayed by the Qt application. I also see the limits of Qt styling system. Some widgets were particularly hard to get right, such as
QTabBar, because Qt’s private code calls the
QStyle methods in a specific order that we can’t modify. For the
QTabBar, as instance, the tabs are drawn in a specific order and it will impact the shape you may give them.
I made sure that my
QStyle library does not contain any company-specific code, so the lib is totally generic and can be used on any Qt application.
I plan to improve it progressively and open-source it, so the Qt community is able to benefit it.