Dark theme for Qt widgets?

60,609

Solution 1

No, but you may use my fairly comprehensive stylesheets that should look excellent on most platforms (it's inspired by KDE's Breeze Theme, which is a dark theme that is quite elegant). This was (hard) forked from the excellent QDarkStylesheet, which I felt had theme issues in numerous areas, so I modified it extensively for my own needs and added a light theme.

Simple Use

A sample of the theme is here. To use it in PyQt5, simply add the following lines to a project:

import sys
from PyQt5.QtCore import QFile, QTextStream
from PyQt5.QtWidgets import QApplication
import breeze_resources

app = QApplication(sys.argv)
file = QFile(":/dark.qss")
file.open(QFile.ReadOnly | QFile.Text)
stream = QTextStream(file)
app.setStyleSheet(stream.readAll())

enter image description here

Dynamic Stylesheet Toggling

In response to a comment, the easiest way to adjust the stylesheet to use either the light or the dark stylesheet dynamically is to wrap it in a function. You may then use the function as a slot to a Qt signal (warning: I primarily develop using C++, so there may be small errors in my code for the signal/slot mechanism).

def toggle_stylesheet(path):
    '''
    Toggle the stylesheet to use the desired path in the Qt resource
    system (prefixed by `:/`) or generically (a path to a file on
    system).

    :path:      A full path to a resource or file on system
    '''

    # get the QApplication instance,  or crash if not set
    app = QApplication.instance()
    if app is None:
        raise RuntimeError("No Qt Application found.")

    file = QFile(path)
    file.open(QFile.ReadOnly | QFile.Text)
    stream = QTextStream(file)
    app.setStyleSheet(stream.readAll())

Now we can add generic application logic that can use this function in a signal/slot mechanism (using a lambda as a convenient wrapper, if needed, to provide the path to the stylesheet toggler):

# add logic for setting up application
app = QApplication(sys.argv)
# more logic for creating top-level widgets, application logic ...

parent = ...
light_btn = QPushButton("Toggle light.", parent)
light_btn.clicked.connect(lambda: toggle_stylesheet(":/light.qss"))

dark_btn = QPushButton("Toggle dark.", parent)
dark_btn.clicked.connect(lambda: toggle_stylesheet(":/dark.qss"))

# add to the layout, do other stuff
# ...

# end the Qt application
sys.exit(app.exec_())

This allows users to dynamically change the theme of an application developed with PyQt5 (or using analogous logic in C++, Qt5) to either a light or dark theme.

Disclaimer: Obviously I am the maintainer.

Solution 2

There's no dark theme built into Qt. But you can quite easily create one yourself with the following code:

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QPalette, QColor


app = QApplication([])
# Force the style to be the same on all OSs:
app.setStyle("Fusion")

# Now use a palette to switch to dark colors:
palette = QPalette()
palette.setColor(QPalette.Window, QColor(53, 53, 53))
palette.setColor(QPalette.WindowText, Qt.white)
palette.setColor(QPalette.Base, QColor(25, 25, 25))
palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
palette.setColor(QPalette.ToolTipBase, Qt.black)
palette.setColor(QPalette.ToolTipText, Qt.white)
palette.setColor(QPalette.Text, Qt.white)
palette.setColor(QPalette.Button, QColor(53, 53, 53))
palette.setColor(QPalette.ButtonText, Qt.white)
palette.setColor(QPalette.BrightText, Qt.red)
palette.setColor(QPalette.Link, QColor(42, 130, 218))
palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
palette.setColor(QPalette.HighlightedText, Qt.black)
app.setPalette(palette)

The nice thing about this is that it introduces no external dependencies. If you're interested what the above changes look like, I created an example PyQt5 app with a dark theme. Here is a screenshot:

Qt dark theme

Solution 3

I was trying to apply this to my fbs based app and found the below easily allowed me to style the app by applying it to the AppContext

class AppContext(ApplicationContext):
    def run(self):
        self.main_window.show()
        return self.app.exec_()

    @cached_property
    def main_window(self):
        return MainWindow(self)

    if theme_selection == 'Dark':
        QApplication.setStyle("Fusion")
        #
        # # Now use a palette to switch to dark colors:
        dark_palette = QPalette()
        dark_palette.setColor(QPalette.Window, QColor(53, 53, 53))
        dark_palette.setColor(QPalette.WindowText, Qt.white)
        dark_palette.setColor(QPalette.Base, QColor(35, 35, 35))
        dark_palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
        dark_palette.setColor(QPalette.ToolTipBase, QColor(25, 25, 25))
        dark_palette.setColor(QPalette.ToolTipText, Qt.white)
        dark_palette.setColor(QPalette.Text, Qt.white)
        dark_palette.setColor(QPalette.Button, QColor(53, 53, 53))
        dark_palette.setColor(QPalette.ButtonText, Qt.white)
        dark_palette.setColor(QPalette.BrightText, Qt.red)
        dark_palette.setColor(QPalette.Link, QColor(42, 130, 218))
        dark_palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
        dark_palette.setColor(QPalette.HighlightedText, QColor(35, 35, 35))
        dark_palette.setColor(QPalette.Active, QPalette.Button, QColor(53, 53, 53))
        dark_palette.setColor(QPalette.Disabled, QPalette.ButtonText, Qt.darkGray)
        dark_palette.setColor(QPalette.Disabled, QPalette.WindowText, Qt.darkGray)
        dark_palette.setColor(QPalette.Disabled, QPalette.Text, Qt.darkGray)
        dark_palette.setColor(QPalette.Disabled, QPalette.Light, QColor(53, 53, 53))
        QApplication.setPalette(dark_palette)
    elif theme_selection == 'Light':
        QApplication.setStyle("")
        pass
    else:
        pass

You can use Qsettings to save a preference for which mode like this and restore on start.

if settings.contains("theme_selection"):
    # there is the key in QSettings
    print('Checking for theme preference in config')
    theme_selection = settings.value('theme_selection')
    print('Found theme_selection in config:' + theme_selection)
else:
    if not is_mac():
        print('theme_selection not found in config. Using default Darkmode')
        settings.setValue('theme_selection', 'Dark')
        theme_selection = settings.value('theme_selection')
    elif is_mac():
        print('theme_selection not found in config. Using default Lightmode')
        settings.setValue('theme_selection', 'Light')
        theme_selection = settings.value('theme_selection')
    pass

Looks amazing could not comment on Michael Herrmann's post to say thanks but did upvote it.

Before: How it looked before dark pallete mode enabled

After: With Pallete mode enabled

Middle part is xterm.js so that is why its still white for now as its not a QT styled thing.

Solution 4

Founded in my bookmarks. I don't remember the original source.

QApplication::setStyle(QStyleFactory::create("Fusion"));
QPalette p;
p = qApp->palette();
p.setColor(QPalette::Window, QColor(53,53,53));
p.setColor(QPalette::Button, QColor(53,53,53));
p.setColor(QPalette::Highlight, QColor(142,45,197));
p.setColor(QPalette::ButtonText, QColor(255,255,255));
qApp->setPalette(p);

P.S. it may be adjusted with QSS if necessary.

Share:
60,609
sunyata
Author by

sunyata

Interested in Buddhism and software development. If you're reading this and is also interested in these things please contact me at tord.dellsen (at) gmail (dot) com, and maybe we can work on an open source Buddhist software project together?

Updated on October 21, 2021

Comments

  • sunyata
    sunyata over 2 years

    Background

    I'm building a PyQt5 application, that I'd like to have a dark theme for. Previously I've worked with Android development where there was a dark theme that I could set for a whole application

    Question

    Is there a dark theme built into Qt (that applies to all widgets in an application, and that is cross-platform)?

  • leetNightshade
    leetNightshade over 5 years
    All of the other dark themes I tried have missing parts and don't look good, are buggy and hacked together, and mess around too much with padding and layout. This one is the best by far. That said it's not perfect, but probably only needs a little adjusting. The Dock list with checkboxes of active docks has the checkboxes overlapping the dock names.
  • Alex Huszagh
    Alex Huszagh over 5 years
    @leetNightshade Feel free to post an issue and I can address it. Feature or pull requests would be wonderful.
  • aoh
    aoh over 5 years
    @AlexanderHuszagh Is it possible to toggle between enabling the dark theme and disabling it? It seems like after I call "sys.exit(app.exec_())" I can no longer set the stylesheet of the app
  • Alex Huszagh
    Alex Huszagh over 5 years
    @aoh, Yes there is, but I currently am on a road trip and have no access to a computer. I'll put this on my todo-list, but a reminder in 5 days would be nice.
  • Alex Huszagh
    Alex Huszagh over 5 years
    @aoh Let me know if that addresses your issues, it explains simply how you can toggle between the light and dark styles at runtime, and to use a native style, you could simply call app.setStyleSheet(""). Binding these to signals in the UI would allow users to change the stylesheet at runtime, preferably storing their preferred UI in a config file.
  • aoh
    aoh over 5 years
    @AlexanderHuszagh It definitely answers my question, thank you!
  • TSeymour
    TSeymour over 3 years
    Using this approach, how would you go back to the standard palette?
  • Mike R
    Mike R over 3 years
    Updated my post to reflect current way im handling both Light/Dark mode with Qsetting.
  • Edward Torvalds
    Edward Torvalds over 3 years
    what about default icons? that has to be taken care separately right?
  • IceFire
    IceFire about 3 years
    Yes, using a dark palette seems to be the solution. What I did: I used my macOS, changed it to dark theme, Qt recognises this (5.12) and renders everything in a dark theme. Then, I extracted all the colors from the palette, and set them, so that I can also use them on Windows. Yes, icons need to be used separately. I have SVGs that are flat/unicolor, so I just run a script to duplicate them, text-replace the color, and have a wrapper function that returns bright/dark icons based on the theme. By the way, I also needed to replace Light/Midlight/Dark/Mid/Shadow of the palette to work properly
  • Abdessabour Mtk
    Abdessabour Mtk over 2 years
    @IceFire I would be interested to see what you did. also Michael great work thanks for this
  • Arnaud
    Arnaud over 2 years
    Thank you for this great answer ! I am not sure if it applies to all versions of Qt but I had to add QToolTip::setPalette(palette); for the settings to take effect on tooltips.
  • MasterHD
    MasterHD almost 2 years
    +1 for making me aware of the "Fusion" QStyleFactory and copying the palette before making changes to it. So much simpler, but the list of setColor calls is incomplete: Base, Dark, ButtonText (disabled), Text (regular/disabled), Button (Active), and possibly others are missing.