How can I correctly apply a change of the global color scheme (light/dark)?
-
Hi all,
I have an application that chooses some colors and icons according to the used color scheme (light/dark). I check the
base
andwindowText
colors to detect the used scheme.Now I wanted to be able to update the colors dynamically, run-time, without having to restart the application when a scheme change is done. I subclassed
QApplication
to catch the change, like this:bool Application::event(QEvent *event) { if (event->type() == QEvent::ApplicationPaletteChange) { updateColorMode(); Q_EMIT paletteChanged(); } return QApplication::event(event); } void Application::updateColorMode() { // Detect if we use a dark theme const QPalette palette; const bool isDarkMode = palette.base().color().lightness() < palette.windowText().color().lightness(); setProperty("DARK_MODE", isDarkMode); }
Changing the color scheme is correctly processed. I thought I now only would have to connect to my
paletteChanged
signal and update the colors/icons/etc. accordingly.However, I noticed that the color change is not propagated correctly all over my application. Using KDE Plasma and Qt 6.7.3 or Qt 5.15.14, my main window looks like this when started in light mode:
After switching to dark mode, the new colors are only partially applied, i.e. the
QDockWidget
s are not updated:I also tried to
setAttribute(Qt::WA_WindowPropagation);
in my main window class, but the result is the same.Interestingly, when I switch back to light mode, the dock widgets get the dark colors, but still, the colors are not correct:
What do I have to do so that the colors are applied correctly? When I close and restart the application, all colors are set correctly, according to the global style.
Thanks for all help!
-
Good question, I've also pondered the same.
Figuring out all the styling related stuff is complicated. We have:
- Qt style engine
- QSS styling
- QPalette
I've never really gathered how it all works together, I understand that the QStyle is basically the implementation to paint the widgets and so on. Not sure how the Palette and QSS mix together however. Rocket science where there should not be any rocket science.
Anyway Personally I've tried to something like this
https://github.com/ensisoft/detonator/blob/dev/editor/app/utility.cpp#L291
It mostly works.
-
I use this property to detect and follow OS change to light/dark. https://doc.qt.io/qt-6/qstylehints.html#colorScheme-prop
Please note restrictions listed.
-
Detecting the change is not the problem – the problem is that the new scheme is not applied everywhere.
Seems like setting a custom style sheet prevents the respective widget from updating itself automatically (e.g. for my above example, I set a style sheet for the dock widgets to make their titles a bit larger).
I haven't figured out yet what I have to do to trigger the style update …
-
Hi,
Style sheets have their own style and you are responsible for handling this use case by hand by updating your style sheet.
Depending on what you are doing with your stylesheet, you might want to implement a proxy style that does the changes you want on top of what the underlying does.
-
Thanks for this hint!
Sticking to the above example, I e.g. set
dock->setStyleSheet(QStringLiteral("QDockWidget { font-weight: bold; font-size: %1pt; }").arg( (int) double(dock->font().pointSize()) * 1.2));
to all dock widgets to make their titles larger. I didn't find any approach to achieve this using something else (QFont etc.), and the style sheets approach worked.
Would one be able to do that using such a proxy style you speak of? Or could I somehow manually set the new application-wide palette and then re-apply my style sheet? If I restart the program, the look is as expected, so applying the style sheets for the dark scheme does work as well as it does for the light one.
-
@l3u_ I grappled with this problem for a while, but eventually reached a satisfying state across Windows, macOS, KDE and Gnome. Though - I didn't use style sheets.
There are two points to this, that might be problems in your implementation.
The first is that the default handling of the
ApplicationPaletteChange
event in the application object is to propagate it to all top-level windows of the application, where each window actually resolves and updates its' palette, and then further propagates it to all of the widgets on it asPaletteChange
events.The implication of this is that ideally you'd want to do any additional touch-ups after all of that has happened. However in your application class you determine the current scheme and emit
paletteChanged
before the defaultQApplication
handling happens, so whatever is happening in connected slots is likely too soon.The second point is that for some platform integrations, changing the system-wide dark mode is not only a palette change, it could actually also be a style change. One of such platforms is exactly KDE Plasma - the light and dark variants of the Breeze theme are actually different
QStyle
implementations, and if you use the same Qt installation as the one KDE itself runs against you are actually using Plasma's platform theme plugin and styles even if you are a pure Qt app (hard to notice that sometimes, "Breeze" and "Fusion" are quite similar visually). This means that you should also be sensitive to theThemeChange
event. There is also aApplicationPaletteChange
event around typically, but the order doesn't seem to be deterministic in my experience so I just applied custom theming on both.The later point might be the problem spot with style sheets, as each stylesheet actually creates a proxy style behind the scenes for you. I'd probably try to re-apply stylesheets after one of the above events happened, but I'm not sure about it.
-
Thanks for the explanation! Actually, I don't do anything with that
paletteChanged
signal, I just put it there because I thought I would use it to update e.g. icons on buttons and such. But I didn't come so far because I noticed that most of my program wasn't changed after the color scheme change.So if setting a style sheet creates a proxy style … would simply applying it again update it, using the current (new) style?!
-
Thanks for the explanation! Actually, I don't do anything with that
paletteChanged
signal, I just put it there because I thought I would use it to update e.g. icons on buttons and such. But I didn't come so far because I noticed that most of my program wasn't changed after the color scheme change.So if setting a style sheet creates a proxy style … would simply applying it again update it, using the current (new) style?!
@l3u_ said in How can I correctly apply a change of the global color scheme (light/dark)?:
So if setting a style sheet creates a proxy style … would simply applying it again update it, using the current (new) style?!
It should, yes, but as said I have no experience with it.
-
Hey, simply adding a
setStyleSheet(styleSheet());
in the QApplication subclass's
if (event->type() == QEvent::ApplicationPaletteChange) {
clause almost does the trick :-) Only the icons and colors set depending on the initial style remain. The rest is already updated with this. Apparently, this causes all the styled widgets to update themselves.Let's see if I can get the rest to work also …