style_sheet.py 6.6 KB
Newer Older
之一Yo's avatar
之一Yo 已提交
1
# coding:utf-8
之一Yo's avatar
之一Yo 已提交
2 3
from enum import Enum
from string import Template
4
from typing import Union
5 6
import weakref

7
from PyQt5.QtCore import QFile, QObject
之一Yo's avatar
之一Yo 已提交
8
from PyQt5.QtGui import QColor
9
from PyQt5.QtWidgets import QWidget
之一Yo's avatar
之一Yo 已提交
10

之一Yo's avatar
之一Yo 已提交
11
from .config import qconfig, Theme, isDarkTheme
12 13


14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
class StyleSheetManager(QObject):
    """ Style sheet manager """

    def __init__(self):
        self.widgets = weakref.WeakKeyDictionary()

    def register(self, file: str, widget: QWidget):
        """ register widget to manager

        Parameters
        ----------
        file: str
            qss file path

        widget: QWidget
            the widget to set style sheet
        """
        if widget not in self.widgets:
            widget.destroyed.connect(self.deregister)

        self.widgets[widget] = file

    def deregister(self, widget: QWidget):
        """ deregister widget from manager """
        if widget not in self.widgets:
            return

        self.widgets.pop(widget)

    def items(self):
        return self.widgets.items()


styleSheetManager = StyleSheetManager()
48

之一Yo's avatar
之一Yo 已提交
49

之一Yo's avatar
之一Yo 已提交
50 51 52 53 54 55
class QssTemplate(Template):
    """ style sheet template """

    delimiter = '--'


之一Yo's avatar
之一Yo 已提交
56 57 58 59 60 61 62 63 64 65 66 67 68 69
def applyThemeColor(qss: str):
    """ apply theme color to style sheet

    Parameters
    ----------
    qss: str
        the style sheet string to apply theme color, the substituted variable
        should be equal to the value of `ThemeColor` and starts width `--`, i.e `--ThemeColorPrimary`
    """
    template = QssTemplate(qss)
    mappings = {c.value: c.name() for c in ThemeColor._member_map_.values()}
    return template.safe_substitute(mappings)


70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
class StyleSheetBase:
    """ Style sheet base class """

    def path(self, theme=Theme.AUTO):
        """ get the path of style sheet """
        raise NotImplementedError

    def content(self, theme=Theme.AUTO):
        """ get the content of style sheet """
        return getStyleSheet(self, theme)

    def apply(self, widget: QWidget, theme=Theme.AUTO):
        """ apply style sheet to widget """
        setStyleSheet(widget, self, theme)


class FluentStyleSheet(StyleSheetBase, Enum):
    """ Fluent style sheet """

    MENU = "menu"
    BUTTON = "button"
    DIALOG = "dialog"
    SLIDER = "slider"
    INFO_BAR = "info_bar"
    SPIN_BOX = "spin_box"
    TOOL_TIP = "tool_tip"
    CHECK_BOX = "check_box"
    COMBO_BOX = "combo_box"
    LINE_EDIT = "line_edit"
之一Yo's avatar
之一Yo 已提交
99
    LIST_VIEW = "list_view"
之一Yo's avatar
之一Yo 已提交
100
    TREE_VIEW = "tree_view"
101
    TABLE_VIEW = "table_view"
之一Yo's avatar
之一Yo 已提交
102
    TIME_PICKER = "time_picker"
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
    SETTING_CARD = "setting_card"
    COLOR_DIALOG = "color_dialog"
    SWITCH_BUTTON = "switch_button"
    MESSAGE_DIALOG = "message_dialog"
    STATE_TOOL_TIP = "state_tool_tip"
    FOLDER_LIST_DIALOG = "folder_list_dialog"
    SETTING_CARD_GROUP = "setting_card_group"
    EXPAND_SETTING_CARD = "expand_setting_card"
    NAVIGATION_INTERFACE = "navigation_interface"

    def path(self, theme=Theme.AUTO):
        theme = qconfig.theme if theme == Theme.AUTO else theme
        return f":/qfluentwidgets/qss/{theme.value.lower()}/{self.value}.qss"


def getStyleSheet(file: Union[str, StyleSheetBase], theme=Theme.AUTO):
之一Yo's avatar
之一Yo 已提交
119
    """ get style sheet
之一Yo's avatar
之一Yo 已提交
120 121 122

    Parameters
    ----------
123 124
    file: str | StyleSheetBase
        qss file
125 126 127

    theme: Theme
        the theme of style sheet
之一Yo's avatar
之一Yo 已提交
128
    """
129 130 131 132
    if isinstance(file, StyleSheetBase):
        file = file.path(theme)

    f = QFile(file)
之一Yo's avatar
之一Yo 已提交
133
    f.open(QFile.ReadOnly)
之一Yo's avatar
之一Yo 已提交
134
    qss = str(f.readAll(), encoding='utf-8')
之一Yo's avatar
之一Yo 已提交
135
    f.close()
之一Yo's avatar
之一Yo 已提交
136

之一Yo's avatar
之一Yo 已提交
137
    return applyThemeColor(qss)
之一Yo's avatar
之一Yo 已提交
138 139


140 141
def setStyleSheet(widget, file: Union[str, StyleSheetBase], theme=Theme.AUTO, register=True):
    """ set the style sheet of widget
之一Yo's avatar
之一Yo 已提交
142 143 144 145 146 147

    Parameters
    ----------
    widget: QWidget
        the widget to set style sheet

148 149
    file: str | StyleSheetBase
        qss file
150 151 152

    theme: Theme
        the theme of style sheet
153 154 155 156

    register: bool
        whether to register the widget to the style manager. If `register=True`, the style of
        the widget will be updated automatically when the theme changes
之一Yo's avatar
之一Yo 已提交
157
    """
158 159 160
    if register:
        styleSheetManager.register(file, widget)

161 162 163
    widget.setStyleSheet(getStyleSheet(file, theme))


164 165
def updateStyleSheet():
    """ update the style sheet of all fluent widgets """
166 167 168 169 170 171 172 173
    removes = []
    for widget, file in styleSheetManager.items():
        try:
            setStyleSheet(widget, file, qconfig.theme)
        except RuntimeError:
            removes.append(widget)

    for widget in removes:
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
        styleSheetManager.deregister(widget)


def setTheme(theme: Theme, save=False):
    """ set the theme of application

    Parameters
    ----------
    theme: Theme
        theme mode

    save: bool
        whether to save the change to config file
    """
    qconfig.set(qconfig.themeMode, theme, save)
    updateStyleSheet()
之一Yo's avatar
之一Yo 已提交
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268


class ThemeColor(Enum):
    """ Theme color type """

    PRIMARY = "ThemeColorPrimary"
    DARK_1 = "ThemeColorDark1"
    DARK_2 = "ThemeColorDark2"
    DARK_3 = "ThemeColorDark3"
    LIGHT_1 = "ThemeColorLight1"
    LIGHT_2 = "ThemeColorLight2"
    LIGHT_3 = "ThemeColorLight3"

    def name(self):
        return self.color().name()

    def color(self):
        color = qconfig.get(qconfig.themeColor)  # type:QColor

        # transform color into hsv space
        h, s, v, _ = color.getHsvF()

        if isDarkTheme():
            s *= 0.84
            v = 1
            if self == self.DARK_1:
                v *= 0.9
            elif self == self.DARK_2:
                s *= 0.977
                v *= 0.82
            elif self == self.DARK_3:
                s *= 0.95
                v *= 0.7
            elif self == self.LIGHT_1:
                s *= 0.92
            elif self == self.LIGHT_2:
                s *= 0.78
            elif self == self.LIGHT_3:
                s *= 0.65
        else:
            if self == self.DARK_1:
                v *= 0.75
            elif self == self.DARK_2:
                s *= 1.05
                v *= 0.5
            elif self == self.DARK_3:
                s *= 1.1
                v *= 0.4
            elif self == self.LIGHT_1:
                v *= 1.05
            elif self == self.LIGHT_2:
                s *= 0.75
                v *= 1.05
            elif self == self.LIGHT_3:
                s *= 0.65
                v *= 1.05

        return QColor.fromHsvF(h, min(s, 1), min(v, 1))


def themeColor():
    """ get theme color """
    return ThemeColor.PRIMARY.color()


def setThemeColor(color, save=False):
    """ set theme color

    Parameters
    ----------
    color: QColor | Qt.GlobalColor | str
        theme color

    save: bool
        whether to save to change to config file
    """
    color = QColor(color)
    qconfig.set(qconfig.themeColor, color, save=save)
    updateStyleSheet()