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
    TREE_VIEW = "tree_view"
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
    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):
116
    """ get style sheet from `qfluentwidgets` embedded qss file
之一Yo's avatar
之一Yo 已提交
117 118 119

    Parameters
    ----------
120 121
    file: str | StyleSheetBase
        qss file
122 123 124

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

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

之一Yo's avatar
之一Yo 已提交
134
    return applyThemeColor(qss)
之一Yo's avatar
之一Yo 已提交
135 136


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

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

145 146
    file: str | StyleSheetBase
        qss file
147 148 149

    theme: Theme
        the theme of style sheet
150 151 152 153

    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 已提交
154
    """
155 156 157
    if register:
        styleSheetManager.register(file, widget)

158 159 160
    widget.setStyleSheet(getStyleSheet(file, theme))


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

    for widget in removes:
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
        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 已提交
187 188 189 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


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()