mne-python
mne-python copied to clipboard
Automatic light/dark mode switching of open STC plot windows
On macOS, apps that support light and dark themes switch between the two if the system theme is changed, without requiring a restart. It would be good if our STC plotters could do the same.
https://user-images.githubusercontent.com/2046265/112053418-9e0b8180-8b54-11eb-8a9a-d49b3e467c45.mov
In this screencast you can also see that the color of the title bar is not correct in dark mode, but that might be a Qt issue?
cc @GuillaumeFavelier
This one to me seems like it will be a lot of work and/or potentially fragile (probably need to hook into some system notification thing) for very small added benefit (since theme changes are generally rare).
This one to me seems like it will be a lot of work and/or potentially fragile (probably need to hook into some system notification thing) for very small added benefit (since theme changes are generally rare).
Yeah, no idea, I thought maybe Qt can trigger a callback upon theme change, no idea.
But the light title bar … would be super nice if we could make this one dark ;)
cc @marsipu
since theme changes are generally rare
plus the STC views are typically not long-lived and can simply be recreated, unlike e.g. a text editor or terminal window, which you may want to keep open for days or weeks.
This one to me seems like it will be a lot of work and/or potentially fragile (probably need to hook into some system notification thing) for very small added benefit (since theme changes are generally rare).
Yeah, no idea, I thought maybe Qt can trigger a callback upon theme change, no idea.
But the light title bar … would be super nice if we could make this one dark ;)
Is the titlebar white when you start in darkmode?
To dynamically change with the mode, one could maybe setup a QTimer and check periodically for a change from darkdetect. But it is maybe questionable as you already said, if it was worth it (and I don't know @GuillaumeFavelier, how that would influence the performance of for example the BackgroundPlotter).
I read some forum-entries about the titlebar, it seems that the titlebar-color is not influenced by Qt but by the OS. There seem to be a workaround in cpp for MacOS, but I don't know if that could be translated to (MNE-)Python. And I found none for Windows or Linux.
But there seems to be another possibility by replacing the whole window-surroundings including the titlebar. as qtmodern does it. If we'd be willing to add another dependency, this may be the best solution, which would also unify the look across platforms as a side-effect. What do you say?
Take a look how I do it in MNELAB. It works on macOS and Linux, but unfortunately Qt doesn't support that on Windows (yet).
Nice, what do you do? Is it the part with the NSBundle in main.py?
No, I'm catching a QEvent here: https://github.com/cbrnr/mnelab/blob/main/mnelab/mainwindow.py#L1011-L1019
This event is triggered whenever the OS theme changes. Therefore, you can use it to change themes on the fly, but as I said not on Windows. However, maybe this isn't that bad, because Windows doesn't have a dynamically changing dark/light theme (it's more like a setting you choose once).
However, since you're using style sheets this will probably not work. I think the white title bar is also due to style sheets.
Nice thank you, maybe it would work if we connect the Stylesheet-Change with your PaletteChange-Event? Could it be, that Qt changes to dark automatically on MacOS and Linux? Or how do you influence the appearance apart from the icons in mnelab? Coming from Windows (unfortunately I have not access to other devices at the moment) I just thought about stylesheets because they seemed to work with all OS.
Nice thank you, maybe it would work if we connect the Stylesheet-Change with your PaletteChange-Event?
It's worth a try.
Could it be, that Qt changes to dark automatically on MacOS and Linux? Or how do you influence the appearance apart from the icons in mnelab?
Exactly, I only change the icons manually. On Windows this doesn't work unfortunately, mainly because Qt still does not provide a native dark theme for Windows (not even Qt6).
Coming from Windows (unfortunately I have not access to other devices at the moment) I just thought about stylesheets because they seemed to work with all OS.
They do (except for the title bar apparently), but style sheets never look like a native app (although I think it's very close).
This one to me seems like it will be a lot of work and/or potentially fragile
I'm catching a QEvent here
Ahh if it's based on some Qt event then ignore what I said about being much work or fragile, it should be neither of those. And even detection of the style name is two lines of Foundation code which seems okay.
Personally my vote would be to use Qt's builtin dark support/styling as much as possible (OSX via icon change) and resort to the style sheet method where Qt does not support it or we don't know how to do it more natively (Windows and Linux).
Personally my vote would be to use Qt's builtin dark support/styling as much as possible (OSX via icon change) and resort to the style sheet method where Qt does not support it or we don't know how to do it more natively (Windows and Linux).
:+1: but native styling works on both macOS and Linux, so style sheets should only be required on Windows.
Even better!
I wonder, why dark mode was not automatically applied already on MacOS and Linux before we added the stylesheets. Could it be, that the dark-mode is not supported yet by the default pyqt-version (5.12.3) we get from conda-forge?
I wonder, why dark mode was not automatically applied already on MacOS and Linux before we added the stylesheets. Could it be, that the dark-mode is not supported yet by the default pyqt-version (5.12.3) we get from conda-forge?
I just tried this with MNELAB, and indeed it seems like 5.12 has not fully implemented theme switching. Only the title bar and icon bar adapt with the theme, but the main widget and status bar remain with the theme they had when I started the app. Therefore, it might be a good idea to use at least 5.15. There's already an open issue: https://github.com/conda-forge/pyqt-feedstock/issues/98
Qt 5.14+ VTK does not render in HiDPI EDIT: and the controls / clicking are broken, which to me is (much) more important than properly setting the colors of the Qt controls / icons / theme:
https://gitlab.kitware.com/vtk/vtk/-/issues/17953
If theme switching works on 5.13 then we can use that, though, since VTK works properly with that.
Also on Linux I've never had my Qt be anything other than light despite using a dark theme (and latest PyQt5, 5.15+), but maybe that's because I use a GTK-based front-end (Gnome-shell and/or cinnamon) rather than KDE. @cbrnr where have you seen the Qt controls be automatically dark on Linux?
Re Qt 5.14+, that's really a pity. Being stuck with an old version will only make things worse over time, so I really hope that VTK fixes this bug (BTW this is another reason why I would want to avoid VTK for a raw viewer and rather stick to pure Qt).
I will test if theme switching works with Qt 5.13, but this would mean that we'd have to mix pip installs with conda, which is never a great idea.
I use GNOME on Arch Linux (so very likely the latest versions of everything), and I'm pretty sure that it works. However, I might be wrong because it's been a while since I've worked on my Linux box (due to WFH), but I can test it next week.
Yes, theme switching on macOS works with Qt 5.13!
There is some suggestion that to trigger it you have to call setStyle, so maybe MNELAB always calls this, and if we do it (on 5.13+) it will work automagically on Qt and/or Linux as well:
https://stackoverflow.com/a/59625439/2175965
I'm not calling setStyle anywhere in MNELAB.
Ahh I thought you did, guess that's not it. Unfortunately for me even on Adaiwata-dark and Qt 5.15.3 mnelab produces a light theme, so I can't investigate on my Linux machine:

I can perhaps investigate on my macOS laptop later. Presumably there is some important difference between what we do for stc.plot (or at least what we did before the stylesheet stuff and things were still light) and what mnelab does...
I'd be very interested in anything you find out! It's weird that you always get a light theme, but maybe that's how it is. I'll let you know as soon as I get to my Linux box!
On PyVistaQt, the event() function of the QMainWindow is available in:
https://github.com/pyvista/pyvistaqt/blob/master/pyvistaqt/window.py#L29-L34
I think it would be possible to open a PR there to emit a signal_palette_changed or something. Then in mne-python, this signal can be connected to the theme switcher :)
Can you not override the event function and catch the signal?
It would be hacky since we don't design the window itself in mne but just borrow the one created by BackgroundPlotter in PyVistaQt.
Hm, to me reimplementing event sounds less hacky than emitting a (more or less) arbitrary signal upstream. What about deriving a custom BackgroundPlotter class where you reimplement event?
Also, I think you can connect the QEvent.PaletteChange signal to another slot?
BackgroundPlotter inherits from QtInteractor and uses composition for QMainWindow. So it could be possible to modify the parameters at __init__ so that it can accept a window object for example and this one would be custom in mne-python. I still think the signal is simpler. Also, I would rather maintain only one version of the plotter if possible xD
Another solution would be to drop BackgroundPlotter completely, use QtInteractor directly and design our own QMainWindow of course :)
I think you can connect the QEvent.PaletteChange signal to another slot?
I don't understand, do you mean directly without intermediate signal?