initial fluent-widgets ui

This commit is contained in:
2025-08-14 18:45:16 +08:00
parent 746e83ab23
commit 4c66886257
1198 changed files with 805339 additions and 0 deletions

View File

@ -0,0 +1,8 @@
from .setting_card import (SettingCard, SwitchSettingCard, RangeSettingCard,
PushSettingCard, ColorSettingCard, HyperlinkCard,
PrimaryPushSettingCard, ColorPickerButton, ComboBoxSettingCard)
from .expand_setting_card import ExpandSettingCard, ExpandGroupSettingCard, SimpleExpandGroupSettingCard
from .folder_list_setting_card import FolderListSettingCard
from .options_setting_card import OptionsSettingCard
from .custom_color_setting_card import CustomColorSettingCard
from .setting_card_group import SettingCardGroup

View File

@ -0,0 +1,139 @@
# coding:utf-8
from typing import Union
from PySide6.QtCore import Qt, Signal
from PySide6.QtGui import QIcon, QColor
from PySide6.QtWidgets import QWidget, QLabel, QButtonGroup, QVBoxLayout, QPushButton, QHBoxLayout
from ..dialog_box import ColorDialog
from .expand_setting_card import ExpandGroupSettingCard
from ..widgets.button import RadioButton
from ...common.config import qconfig, ColorConfigItem
from ...common.icon import FluentIconBase
class CustomColorSettingCard(ExpandGroupSettingCard):
""" Custom color setting card """
colorChanged = Signal(QColor)
def __init__(self, configItem: ColorConfigItem, icon: Union[str, QIcon, FluentIconBase], title: str,
content=None, parent=None, enableAlpha=False):
"""
Parameters
----------
configItem: ColorConfigItem
options config item
icon: str | QIcon | FluentIconBase
the icon to be drawn
title: str
the title of setting card
content: str
the content of setting card
parent: QWidget
parent window
enableAlpha: bool
whether to enable the alpha channel
"""
super().__init__(icon, title, content, parent=parent)
self.enableAlpha = enableAlpha
self.configItem = configItem
self.defaultColor = QColor(configItem.defaultValue)
self.customColor = QColor(qconfig.get(configItem))
self.choiceLabel = QLabel(self)
self.radioWidget = QWidget(self.view)
self.radioLayout = QVBoxLayout(self.radioWidget)
self.defaultRadioButton = RadioButton(
self.tr('Default color'), self.radioWidget)
self.customRadioButton = RadioButton(
self.tr('Custom color'), self.radioWidget)
self.buttonGroup = QButtonGroup(self)
self.customColorWidget = QWidget(self.view)
self.customColorLayout = QHBoxLayout(self.customColorWidget)
self.customLabel = QLabel(
self.tr('Custom color'), self.customColorWidget)
self.chooseColorButton = QPushButton(
self.tr('Choose color'), self.customColorWidget)
self.__initWidget()
def __initWidget(self):
self.__initLayout()
if self.defaultColor != self.customColor:
self.customRadioButton.setChecked(True)
self.chooseColorButton.setEnabled(True)
else:
self.defaultRadioButton.setChecked(True)
self.chooseColorButton.setEnabled(False)
self.choiceLabel.setText(self.buttonGroup.checkedButton().text())
self.choiceLabel.adjustSize()
self.choiceLabel.setObjectName("titleLabel")
self.customLabel.setObjectName("titleLabel")
self.chooseColorButton.setObjectName('chooseColorButton')
self.buttonGroup.buttonClicked.connect(self.__onRadioButtonClicked)
self.chooseColorButton.clicked.connect(self.__showColorDialog)
def __initLayout(self):
self.addWidget(self.choiceLabel)
self.radioLayout.setSpacing(19)
self.radioLayout.setAlignment(Qt.AlignTop)
self.radioLayout.setContentsMargins(48, 18, 0, 18)
self.buttonGroup.addButton(self.customRadioButton)
self.buttonGroup.addButton(self.defaultRadioButton)
self.radioLayout.addWidget(self.customRadioButton)
self.radioLayout.addWidget(self.defaultRadioButton)
self.radioLayout.setSizeConstraint(QVBoxLayout.SetMinimumSize)
self.customColorLayout.setContentsMargins(48, 18, 44, 18)
self.customColorLayout.addWidget(self.customLabel, 0, Qt.AlignLeft)
self.customColorLayout.addWidget(self.chooseColorButton, 0, Qt.AlignRight)
self.customColorLayout.setSizeConstraint(QHBoxLayout.SetMinimumSize)
self.viewLayout.setSpacing(0)
self.viewLayout.setContentsMargins(0, 0, 0, 0)
self.addGroupWidget(self.radioWidget)
self.addGroupWidget(self.customColorWidget)
def __onRadioButtonClicked(self, button: RadioButton):
""" radio button clicked slot """
if button.text() == self.choiceLabel.text():
return
self.choiceLabel.setText(button.text())
self.choiceLabel.adjustSize()
if button is self.defaultRadioButton:
self.chooseColorButton.setDisabled(True)
qconfig.set(self.configItem, self.defaultColor)
if self.defaultColor != self.customColor:
self.colorChanged.emit(self.defaultColor)
else:
self.chooseColorButton.setDisabled(False)
qconfig.set(self.configItem, self.customColor)
if self.defaultColor != self.customColor:
self.colorChanged.emit(self.customColor)
def __showColorDialog(self):
""" show color dialog """
w = ColorDialog(
qconfig.get(self.configItem), self.tr('Choose color'), self.window(), self.enableAlpha)
w.colorChanged.connect(self.__onCustomColorChanged)
w.exec()
def __onCustomColorChanged(self, color):
""" custom color changed slot """
qconfig.set(self.configItem, color)
self.customColor = QColor(color)
self.colorChanged.emit(color)

View File

@ -0,0 +1,390 @@
# coding:utf-8
from typing import List, Union
from PySide6.QtCore import QEvent, Qt, QPropertyAnimation, Property, QEasingCurve, QRectF
from PySide6.QtGui import QColor, QPainter, QIcon, QPainterPath
from PySide6.QtWidgets import QFrame, QWidget, QAbstractButton, QApplication, QScrollArea, QVBoxLayout
from ...common.config import isDarkTheme
from ...common.icon import FluentIcon as FIF
from ...common.style_sheet import FluentStyleSheet
from .setting_card import SettingCard
from ..layout.v_box_layout import VBoxLayout
class ExpandButton(QAbstractButton):
""" Expand button """
def __init__(self, parent=None):
super().__init__(parent)
self.setFixedSize(30, 30)
self.__angle = 0
self.isHover = False
self.isPressed = False
self.rotateAni = QPropertyAnimation(self, b'angle', self)
self.clicked.connect(self.__onClicked)
def paintEvent(self, e):
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing |
QPainter.SmoothPixmapTransform)
painter.setPen(Qt.NoPen)
# draw background
r = 255 if isDarkTheme() else 0
color = Qt.transparent
if self.isEnabled():
if self.isPressed:
color = QColor(r, r, r, 10)
elif self.isHover:
color = QColor(r, r, r, 14)
else:
painter.setOpacity(0.36)
painter.setBrush(color)
painter.drawRoundedRect(self.rect(), 4, 4)
# draw icon
painter.translate(self.width()//2, self.height()//2)
painter.rotate(self.__angle)
FIF.ARROW_DOWN.render(painter, QRectF(-5, -5, 9.6, 9.6))
def enterEvent(self, e):
self.setHover(True)
def leaveEvent(self, e):
self.setHover(False)
def mousePressEvent(self, e):
super().mousePressEvent(e)
self.setPressed(True)
def mouseReleaseEvent(self, e):
super().mouseReleaseEvent(e)
self.setPressed(False)
def setHover(self, isHover: bool):
self.isHover = isHover
self.update()
def setPressed(self, isPressed: bool):
self.isPressed = isPressed
self.update()
def __onClicked(self):
self.setExpand(self.angle < 180)
def setExpand(self, isExpand: bool):
self.rotateAni.stop()
self.rotateAni.setEndValue(180 if isExpand else 0)
self.rotateAni.setDuration(200)
self.rotateAni.start()
def getAngle(self):
return self.__angle
def setAngle(self, angle):
self.__angle = angle
self.update()
angle = Property(float, getAngle, setAngle)
class SpaceWidget(QWidget):
""" Spacing widget """
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setFixedHeight(1)
class HeaderSettingCard(SettingCard):
""" Header setting card """
def __init__(self, icon, title, content=None, parent=None):
super().__init__(icon, title, content, parent)
self.expandButton = ExpandButton(self)
self.hBoxLayout.addWidget(self.expandButton, 0, Qt.AlignRight)
self.hBoxLayout.addSpacing(8)
self.titleLabel.setObjectName("titleLabel")
self.installEventFilter(self)
def eventFilter(self, obj, e):
if obj is self:
if e.type() == QEvent.Enter:
self.expandButton.setHover(True)
elif e.type() == QEvent.Leave:
self.expandButton.setHover(False)
elif e.type() == QEvent.MouseButtonPress and e.button() == Qt.LeftButton:
self.expandButton.setPressed(True)
elif e.type() == QEvent.MouseButtonRelease and e.button() == Qt.LeftButton:
self.expandButton.setPressed(False)
self.expandButton.click()
return super().eventFilter(obj, e)
def addWidget(self, widget: QWidget):
""" add widget to tail """
N = self.hBoxLayout.count()
self.hBoxLayout.removeItem(self.hBoxLayout.itemAt(N - 1))
self.hBoxLayout.addWidget(widget, 0, Qt.AlignRight)
self.hBoxLayout.addSpacing(19)
self.hBoxLayout.addWidget(self.expandButton, 0, Qt.AlignRight)
self.hBoxLayout.addSpacing(8)
def paintEvent(self, e):
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing)
painter.setPen(Qt.NoPen)
if isDarkTheme():
painter.setBrush(QColor(255, 255, 255, 13))
else:
painter.setBrush(QColor(255, 255, 255, 170))
p = self.parent() # type: ExpandSettingCard
path = QPainterPath()
path.setFillRule(Qt.WindingFill)
path.addRoundedRect(QRectF(self.rect().adjusted(1, 1, -1, -1)), 6, 6)
# set the bottom border radius to 0 if parent is expanded
if p.isExpand:
path.addRect(1, self.height() - 8, self.width() - 2, 8)
painter.drawPath(path.simplified())
class ExpandBorderWidget(QWidget):
""" Expand setting card border widget """
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setAttribute(Qt.WA_TransparentForMouseEvents)
parent.installEventFilter(self)
def eventFilter(self, obj, e):
if obj is self.parent() and e.type() == QEvent.Resize:
self.resize(e.size())
return super().eventFilter(obj, e)
def paintEvent(self, e):
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing)
painter.setBrush(Qt.NoBrush)
if isDarkTheme():
painter.setPen(QColor(0, 0, 0, 50))
else:
painter.setPen(QColor(0, 0, 0, 19))
p = self.parent() # type: ExpandSettingCard
r, d = 6, 12
ch, h, w = p.card.height(), self.height(), self.width()
# only draw rounded rect if parent is not expanded
painter.drawRoundedRect(self.rect().adjusted(1, 1, -1, -1), r, r)
# draw the seperator line under card widget
if ch < h:
painter.drawLine(1, ch, w - 1, ch)
class ExpandSettingCard(QScrollArea):
""" Expandable setting card """
def __init__(self, icon: Union[str, QIcon, FIF], title: str, content: str = None, parent=None):
super().__init__(parent=parent)
self.isExpand = False
self.scrollWidget = QFrame(self)
self.view = QFrame(self.scrollWidget)
self.card = HeaderSettingCard(icon, title, content, self)
self.scrollLayout = QVBoxLayout(self.scrollWidget)
self.viewLayout = QVBoxLayout(self.view)
self.spaceWidget = SpaceWidget(self.scrollWidget)
self.borderWidget = ExpandBorderWidget(self)
# expand animation
self.expandAni = QPropertyAnimation(self.verticalScrollBar(), b'value', self)
self.__initWidget()
def __initWidget(self):
""" initialize widgets """
self.setWidget(self.scrollWidget)
self.setWidgetResizable(True)
self.setFixedHeight(self.card.height())
self.setViewportMargins(0, self.card.height(), 0, 0)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
# initialize layout
self.scrollLayout.setContentsMargins(0, 0, 0, 0)
self.scrollLayout.setSpacing(0)
self.scrollLayout.addWidget(self.view)
self.scrollLayout.addWidget(self.spaceWidget)
# initialize expand animation
self.expandAni.setEasingCurve(QEasingCurve.OutQuad)
self.expandAni.setDuration(200)
# initialize style sheet
self.view.setObjectName('view')
self.scrollWidget.setObjectName('scrollWidget')
self.setProperty('isExpand', False)
FluentStyleSheet.EXPAND_SETTING_CARD.apply(self.card)
FluentStyleSheet.EXPAND_SETTING_CARD.apply(self)
self.card.installEventFilter(self)
self.expandAni.valueChanged.connect(self._onExpandValueChanged)
self.card.expandButton.clicked.connect(self.toggleExpand)
def addWidget(self, widget: QWidget):
""" add widget to tail """
self.card.addWidget(widget)
def wheelEvent(self, e):
e.ignore()
def setExpand(self, isExpand: bool):
""" set the expand status of card """
if self.isExpand == isExpand:
return
# update style sheet
self.isExpand = isExpand
self.setProperty('isExpand', isExpand)
self.setStyle(QApplication.style())
# start expand animation
if isExpand:
h = self.viewLayout.sizeHint().height()
self.verticalScrollBar().setValue(h)
self.expandAni.setStartValue(h)
self.expandAni.setEndValue(0)
else:
self.expandAni.setStartValue(0)
self.expandAni.setEndValue(self.verticalScrollBar().maximum())
self.expandAni.start()
self.card.expandButton.setExpand(isExpand)
def toggleExpand(self):
""" toggle expand status """
self.setExpand(not self.isExpand)
def resizeEvent(self, e):
self.card.resize(self.width(), self.card.height())
self.scrollWidget.resize(self.width(), self.scrollWidget.height())
def _onExpandValueChanged(self):
vh = self.viewLayout.sizeHint().height()
h = self.viewportMargins().top()
self.setFixedHeight(max(h + vh - self.verticalScrollBar().value(), h))
def _adjustViewSize(self):
""" adjust view size """
h = self.viewLayout.sizeHint().height()
self.spaceWidget.setFixedHeight(h)
if self.isExpand:
self.setFixedHeight(self.card.height()+h)
def setValue(self, value):
""" set the value of config item """
pass
class GroupSeparator(QWidget):
""" group separator """
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setFixedHeight(3)
def paintEvent(self, e):
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing)
if isDarkTheme():
painter.setPen(QColor(0, 0, 0, 50))
else:
painter.setPen(QColor(0, 0, 0, 19))
painter.drawLine(0, 1, self.width(), 1)
class ExpandGroupSettingCard(ExpandSettingCard):
""" Expand group setting card """
def __init__(self, icon: Union[str, QIcon, FIF], title: str, content: str = None, parent=None):
super().__init__(icon, title, content, parent)
self.widgets = [] # type: List[QWidget]
self.viewLayout.setContentsMargins(0, 0, 0, 0)
self.viewLayout.setSpacing(0)
def addGroupWidget(self, widget: QWidget):
""" add widget to group """
# add separator
if self.viewLayout.count() >= 1:
self.viewLayout.addWidget(GroupSeparator(self.view))
widget.setParent(self.view)
self.widgets.append(widget)
self.viewLayout.addWidget(widget)
self._adjustViewSize()
def removeGroupWidget(self, widget: QWidget):
""" remove a group from card """
if widget not in self.widgets:
return
layoutIndex = self.viewLayout.indexOf(widget)
index = self.widgets.index(widget)
self.viewLayout.removeWidget(widget)
self.widgets.remove(widget)
if not self.widgets:
return self._adjustViewSize()
# remove separator
if layoutIndex >= 1:
separator = self.viewLayout.itemAt(layoutIndex - 1).widget()
separator.deleteLater()
self.viewLayout.removeWidget(separator)
elif index == 0:
separator = self.viewLayout.itemAt(0).widget()
separator.deleteLater()
self.viewLayout.removeWidget(separator)
self._adjustViewSize()
def _adjustViewSize(self):
""" adjust view size """
h = sum(w.sizeHint().height() + 3 for w in self.widgets)
self.spaceWidget.setFixedHeight(h)
if self.isExpand:
self.setFixedHeight(self.card.height()+h)
class SimpleExpandGroupSettingCard(ExpandGroupSettingCard):
""" Simple expand group setting card """
def _adjustViewSize(self):
""" adjust view size """
h = self.viewLayout.sizeHint().height()
self.spaceWidget.setFixedHeight(h)
if self.isExpand:
self.setFixedHeight(self.card.height()+h)

View File

@ -0,0 +1,134 @@
# coding:utf-8
from typing import List
from pathlib import Path
from PySide6.QtCore import Qt, Signal, QSize
from PySide6.QtGui import QPainter, QIcon
from PySide6.QtWidgets import (QPushButton, QFileDialog, QWidget, QLabel,
QHBoxLayout, QToolButton, QSizePolicy)
from ...components.widgets.button import ToolButton, PushButton
from ...common.config import ConfigItem, qconfig
from ...common.icon import drawIcon
from ...common.icon import FluentIcon as FIF
from ..dialog_box.dialog import Dialog
from .expand_setting_card import ExpandSettingCard
class FolderItem(QWidget):
""" Folder item """
removed = Signal(QWidget)
def __init__(self, folder: str, parent=None):
super().__init__(parent=parent)
self.folder = folder
self.hBoxLayout = QHBoxLayout(self)
self.folderLabel = QLabel(folder, self)
self.removeButton = ToolButton(FIF.CLOSE, self)
self.removeButton.setFixedSize(39, 29)
self.removeButton.setIconSize(QSize(12, 12))
self.setFixedHeight(53)
self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed)
self.hBoxLayout.setContentsMargins(48, 0, 60, 0)
self.hBoxLayout.addWidget(self.folderLabel, 0, Qt.AlignLeft)
self.hBoxLayout.addSpacing(16)
self.hBoxLayout.addStretch(1)
self.hBoxLayout.addWidget(self.removeButton, 0, Qt.AlignRight)
self.hBoxLayout.setAlignment(Qt.AlignVCenter)
self.removeButton.clicked.connect(
lambda: self.removed.emit(self))
class FolderListSettingCard(ExpandSettingCard):
""" Folder list setting card """
folderChanged = Signal(list)
def __init__(self, configItem: ConfigItem, title: str, content: str = None, directory="./", parent=None):
"""
Parameters
----------
configItem: RangeConfigItem
configuration item operated by the card
title: str
the title of card
content: str
the content of card
directory: str
working directory of file dialog
parent: QWidget
parent widget
"""
super().__init__(FIF.FOLDER, title, content, parent)
self.configItem = configItem
self._dialogDirectory = directory
self.addFolderButton = PushButton(self.tr('Add folder'), self, FIF.FOLDER_ADD)
self.folders = qconfig.get(configItem).copy() # type:List[str]
self.__initWidget()
def __initWidget(self):
self.addWidget(self.addFolderButton)
# initialize layout
self.viewLayout.setSpacing(0)
self.viewLayout.setAlignment(Qt.AlignTop)
self.viewLayout.setContentsMargins(0, 0, 0, 0)
for folder in self.folders:
self.__addFolderItem(folder)
self.addFolderButton.clicked.connect(self.__showFolderDialog)
def __showFolderDialog(self):
""" show folder dialog """
folder = QFileDialog.getExistingDirectory(
self, self.tr("Choose folder"), self._dialogDirectory)
if not folder or folder in self.folders:
return
self.__addFolderItem(folder)
self.folders.append(folder)
qconfig.set(self.configItem, self.folders)
self.folderChanged.emit(self.folders)
def __addFolderItem(self, folder: str):
""" add folder item """
item = FolderItem(folder, self.view)
item.removed.connect(self.__showConfirmDialog)
self.viewLayout.addWidget(item)
item.show()
self._adjustViewSize()
def __showConfirmDialog(self, item: FolderItem):
""" show confirm dialog """
name = Path(item.folder).name
title = self.tr('Are you sure you want to delete the folder?')
content = self.tr("If you delete the ") + f'"{name}"' + \
self.tr(" folder and remove it from the list, the folder will no "
"longer appear in the list, but will not be deleted.")
w = Dialog(title, content, self.window())
w.yesSignal.connect(lambda: self.__removeFolder(item))
w.exec_()
def __removeFolder(self, item: FolderItem):
""" remove folder """
if item.folder not in self.folders:
return
self.folders.remove(item.folder)
self.viewLayout.removeWidget(item)
item.deleteLater()
self._adjustViewSize()
self.folderChanged.emit(self.folders)
qconfig.set(self.configItem, self.folders)

View File

@ -0,0 +1,86 @@
# coding:utf-8
from typing import Union
from PySide6.QtCore import Signal
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QButtonGroup, QLabel
from ...common.config import OptionsConfigItem, qconfig
from ...common.icon import FluentIconBase
from ..widgets.button import RadioButton
from .expand_setting_card import ExpandSettingCard
class OptionsSettingCard(ExpandSettingCard):
""" setting card with a group of options """
optionChanged = Signal(OptionsConfigItem)
def __init__(self, configItem, icon: Union[str, QIcon, FluentIconBase], title, content=None, texts=None, parent=None):
"""
Parameters
----------
configItem: OptionsConfigItem
options config item
icon: str | QIcon | FluentIconBase
the icon to be drawn
title: str
the title of setting card
content: str
the content of setting card
texts: List[str]
the texts of radio buttons
parent: QWidget
parent window
"""
super().__init__(icon, title, content, parent)
self.texts = texts or []
self.configItem = configItem
self.configName = configItem.name
self.choiceLabel = QLabel(self)
self.buttonGroup = QButtonGroup(self)
self.choiceLabel.setObjectName("titleLabel")
self.addWidget(self.choiceLabel)
# create buttons
self.viewLayout.setSpacing(19)
self.viewLayout.setContentsMargins(48, 18, 0, 18)
for text, option in zip(texts, configItem.options):
button = RadioButton(text, self.view)
self.buttonGroup.addButton(button)
self.viewLayout.addWidget(button)
button.setProperty(self.configName, option)
self._adjustViewSize()
self.setValue(qconfig.get(self.configItem))
configItem.valueChanged.connect(self.setValue)
self.buttonGroup.buttonClicked.connect(self.__onButtonClicked)
def __onButtonClicked(self, button: RadioButton):
""" button clicked slot """
if button.text() == self.choiceLabel.text():
return
value = button.property(self.configName)
qconfig.set(self.configItem, value)
self.choiceLabel.setText(button.text())
self.choiceLabel.adjustSize()
self.optionChanged.emit(self.configItem)
def setValue(self, value):
""" select button according to the value """
qconfig.set(self.configItem, value)
for button in self.buttonGroup.buttons():
isChecked = button.property(self.configName) == value
button.setChecked(isChecked)
if isChecked:
self.choiceLabel.setText(button.text())
self.choiceLabel.adjustSize()

View File

@ -0,0 +1,449 @@
# coding:utf-8
from typing import Union
from PySide6.QtCore import Qt, Signal
from PySide6.QtGui import QColor, QIcon, QPainter
from PySide6.QtWidgets import QFrame, QHBoxLayout, QLabel, QToolButton, QVBoxLayout, QPushButton
from PySide6.QtSvgWidgets import QSvgWidget
from ..dialog_box.color_dialog import ColorDialog
from ..widgets.combo_box import ComboBox
from ..widgets.switch_button import SwitchButton, IndicatorPosition
from ..widgets.slider import Slider
from ..widgets.icon_widget import IconWidget
from ..widgets.button import HyperlinkButton
from ...common.style_sheet import FluentStyleSheet
from ...common.config import qconfig, isDarkTheme, ConfigItem, OptionsConfigItem
from ...common.icon import FluentIconBase, drawIcon
class SettingIconWidget(IconWidget):
def paintEvent(self, e):
painter = QPainter(self)
if not self.isEnabled():
painter.setOpacity(0.36)
painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
drawIcon(self._icon, painter, self.rect())
class SettingCard(QFrame):
""" Setting card """
def __init__(self, icon: Union[str, QIcon, FluentIconBase], title, content=None, parent=None):
"""
Parameters
----------
icon: str | QIcon | FluentIconBase
the icon to be drawn
title: str
the title of card
content: str
the content of card
parent: QWidget
parent widget
"""
super().__init__(parent=parent)
self.iconLabel = SettingIconWidget(icon, self)
self.titleLabel = QLabel(title, self)
self.contentLabel = QLabel(content or '', self)
self.hBoxLayout = QHBoxLayout(self)
self.vBoxLayout = QVBoxLayout()
if not content:
self.contentLabel.hide()
self.setFixedHeight(70 if content else 50)
self.iconLabel.setFixedSize(16, 16)
# initialize layout
self.hBoxLayout.setSpacing(0)
self.hBoxLayout.setContentsMargins(16, 0, 0, 0)
self.hBoxLayout.setAlignment(Qt.AlignVCenter)
self.vBoxLayout.setSpacing(0)
self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
self.vBoxLayout.setAlignment(Qt.AlignVCenter)
self.hBoxLayout.addWidget(self.iconLabel, 0, Qt.AlignLeft)
self.hBoxLayout.addSpacing(16)
self.hBoxLayout.addLayout(self.vBoxLayout)
self.vBoxLayout.addWidget(self.titleLabel, 0, Qt.AlignLeft)
self.vBoxLayout.addWidget(self.contentLabel, 0, Qt.AlignLeft)
self.hBoxLayout.addSpacing(16)
self.hBoxLayout.addStretch(1)
self.contentLabel.setObjectName('contentLabel')
FluentStyleSheet.SETTING_CARD.apply(self)
def setTitle(self, title: str):
""" set the title of card """
self.titleLabel.setText(title)
def setContent(self, content: str):
""" set the content of card """
self.contentLabel.setText(content)
self.contentLabel.setVisible(bool(content))
def setValue(self, value):
""" set the value of config item """
pass
def setIconSize(self, width: int, height: int):
""" set the icon fixed size """
self.iconLabel.setFixedSize(width, height)
def paintEvent(self, e):
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing)
if isDarkTheme():
painter.setBrush(QColor(255, 255, 255, 13))
painter.setPen(QColor(0, 0, 0, 50))
else:
painter.setBrush(QColor(255, 255, 255, 170))
painter.setPen(QColor(0, 0, 0, 19))
painter.drawRoundedRect(self.rect().adjusted(1, 1, -1, -1), 6, 6)
class SwitchSettingCard(SettingCard):
""" Setting card with switch button """
checkedChanged = Signal(bool)
def __init__(self, icon: Union[str, QIcon, FluentIconBase], title, content=None,
configItem: ConfigItem = None, parent=None):
"""
Parameters
----------
icon: str | QIcon | FluentIconBase
the icon to be drawn
title: str
the title of card
content: str
the content of card
configItem: ConfigItem
configuration item operated by the card
parent: QWidget
parent widget
"""
super().__init__(icon, title, content, parent)
self.configItem = configItem
self.switchButton = SwitchButton(
self.tr('Off'), self, IndicatorPosition.RIGHT)
if configItem:
self.setValue(qconfig.get(configItem))
configItem.valueChanged.connect(self.setValue)
# add switch button to layout
self.hBoxLayout.addWidget(self.switchButton, 0, Qt.AlignRight)
self.hBoxLayout.addSpacing(16)
self.switchButton.checkedChanged.connect(self.__onCheckedChanged)
def __onCheckedChanged(self, isChecked: bool):
""" switch button checked state changed slot """
self.setValue(isChecked)
self.checkedChanged.emit(isChecked)
def setValue(self, isChecked: bool):
if self.configItem:
qconfig.set(self.configItem, isChecked)
self.switchButton.setChecked(isChecked)
self.switchButton.setText(
self.tr('On') if isChecked else self.tr('Off'))
def setChecked(self, isChecked: bool):
self.setValue(isChecked)
def isChecked(self):
return self.switchButton.isChecked()
class RangeSettingCard(SettingCard):
""" Setting card with a slider """
valueChanged = Signal(int)
def __init__(self, configItem, icon: Union[str, QIcon, FluentIconBase], title, content=None, parent=None):
"""
Parameters
----------
configItem: RangeConfigItem
configuration item operated by the card
icon: str | QIcon | FluentIconBase
the icon to be drawn
title: str
the title of card
content: str
the content of card
parent: QWidget
parent widget
"""
super().__init__(icon, title, content, parent)
self.configItem = configItem
self.slider = Slider(Qt.Horizontal, self)
self.valueLabel = QLabel(self)
self.slider.setMinimumWidth(268)
self.slider.setSingleStep(1)
self.slider.setRange(*configItem.range)
self.slider.setValue(configItem.value)
self.valueLabel.setNum(configItem.value)
self.hBoxLayout.addStretch(1)
self.hBoxLayout.addWidget(self.valueLabel, 0, Qt.AlignRight)
self.hBoxLayout.addSpacing(6)
self.hBoxLayout.addWidget(self.slider, 0, Qt.AlignRight)
self.hBoxLayout.addSpacing(16)
self.valueLabel.setObjectName('valueLabel')
configItem.valueChanged.connect(self.setValue)
self.slider.valueChanged.connect(self.__onValueChanged)
def __onValueChanged(self, value: int):
""" slider value changed slot """
self.setValue(value)
self.valueChanged.emit(value)
def setValue(self, value):
qconfig.set(self.configItem, value)
self.valueLabel.setNum(value)
self.valueLabel.adjustSize()
self.slider.setValue(value)
class PushSettingCard(SettingCard):
""" Setting card with a push button """
clicked = Signal()
def __init__(self, text, icon: Union[str, QIcon, FluentIconBase], title, content=None, parent=None):
"""
Parameters
----------
text: str
the text of push button
icon: str | QIcon | FluentIconBase
the icon to be drawn
title: str
the title of card
content: str
the content of card
parent: QWidget
parent widget
"""
super().__init__(icon, title, content, parent)
self.button = QPushButton(text, self)
self.hBoxLayout.addWidget(self.button, 0, Qt.AlignRight)
self.hBoxLayout.addSpacing(16)
self.button.clicked.connect(self.clicked)
class PrimaryPushSettingCard(PushSettingCard):
""" Push setting card with primary color """
def __init__(self, text, icon, title, content=None, parent=None):
super().__init__(text, icon, title, content, parent)
self.button.setObjectName('primaryButton')
class HyperlinkCard(SettingCard):
""" Hyperlink card """
def __init__(self, url, text, icon: Union[str, QIcon, FluentIconBase], title, content=None, parent=None):
"""
Parameters
----------
url: str
the url to be opened
text: str
text of url
icon: str | QIcon | FluentIconBase
the icon to be drawn
title: str
the title of card
content: str
the content of card
text: str
the text of push button
parent: QWidget
parent widget
"""
super().__init__(icon, title, content, parent)
self.linkButton = HyperlinkButton(url, text, self)
self.hBoxLayout.addWidget(self.linkButton, 0, Qt.AlignRight)
self.hBoxLayout.addSpacing(16)
class ColorPickerButton(QToolButton):
""" Color picker button """
colorChanged = Signal(QColor)
def __init__(self, color: QColor, title: str, parent=None, enableAlpha=False):
super().__init__(parent=parent)
self.title = title
self.enableAlpha = enableAlpha
self.setFixedSize(96, 32)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setColor(color)
self.setCursor(Qt.PointingHandCursor)
self.clicked.connect(self.__showColorDialog)
def __showColorDialog(self):
""" show color dialog """
w = ColorDialog(self.color, self.tr(
'Choose ')+self.title, self.window(), self.enableAlpha)
w.colorChanged.connect(self.__onColorChanged)
w.exec()
def __onColorChanged(self, color):
""" color changed slot """
self.setColor(color)
self.colorChanged.emit(color)
def setColor(self, color):
""" set color """
self.color = QColor(color)
self.update()
def paintEvent(self, e):
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing)
pc = QColor(255, 255, 255, 10) if isDarkTheme() else QColor(234, 234, 234)
painter.setPen(pc)
color = QColor(self.color)
if not self.enableAlpha:
color.setAlpha(255)
painter.setBrush(color)
painter.drawRoundedRect(self.rect().adjusted(1, 1, -1, -1), 5, 5)
class ColorSettingCard(SettingCard):
""" Setting card with color picker """
colorChanged = Signal(QColor)
def __init__(self, configItem, icon: Union[str, QIcon, FluentIconBase],
title: str, content: str = None, parent=None, enableAlpha=False):
"""
Parameters
----------
configItem: RangeConfigItem
configuration item operated by the card
icon: str | QIcon | FluentIconBase
the icon to be drawn
title: str
the title of card
content: str
the content of card
parent: QWidget
parent widget
enableAlpha: bool
whether to enable the alpha channel
"""
super().__init__(icon, title, content, parent)
self.configItem = configItem
self.colorPicker = ColorPickerButton(
qconfig.get(configItem), title, self, enableAlpha)
self.hBoxLayout.addWidget(self.colorPicker, 0, Qt.AlignRight)
self.hBoxLayout.addSpacing(16)
self.colorPicker.colorChanged.connect(self.__onColorChanged)
configItem.valueChanged.connect(self.setValue)
def __onColorChanged(self, color: QColor):
qconfig.set(self.configItem, color)
self.colorChanged.emit(color)
def setValue(self, color: QColor):
self.colorPicker.setColor(color)
qconfig.set(self.configItem, color)
class ComboBoxSettingCard(SettingCard):
""" Setting card with a combo box """
def __init__(self, configItem: OptionsConfigItem, icon: Union[str, QIcon, FluentIconBase], title, content=None, texts=None, parent=None):
"""
Parameters
----------
configItem: OptionsConfigItem
configuration item operated by the card
icon: str | QIcon | FluentIconBase
the icon to be drawn
title: str
the title of card
content: str
the content of card
texts: List[str]
the text of items
parent: QWidget
parent widget
"""
super().__init__(icon, title, content, parent)
self.configItem = configItem
self.comboBox = ComboBox(self)
self.hBoxLayout.addWidget(self.comboBox, 0, Qt.AlignRight)
self.hBoxLayout.addSpacing(16)
self.optionToText = {o: t for o, t in zip(configItem.options, texts)}
for text, option in zip(texts, configItem.options):
self.comboBox.addItem(text, userData=option)
self.comboBox.setCurrentText(self.optionToText[qconfig.get(configItem)])
self.comboBox.currentIndexChanged.connect(self._onCurrentIndexChanged)
configItem.valueChanged.connect(self.setValue)
def _onCurrentIndexChanged(self, index: int):
qconfig.set(self.configItem, self.comboBox.itemData(index))
def setValue(self, value):
if value not in self.optionToText:
return
self.comboBox.setCurrentText(self.optionToText[value])
qconfig.set(self.configItem, value)

View File

@ -0,0 +1,48 @@
# coding:utf-8
from typing import List
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QWidget, QLabel, QVBoxLayout
from ...common.style_sheet import FluentStyleSheet
from ...common.font import setFont
from ..layout.expand_layout import ExpandLayout
class SettingCardGroup(QWidget):
""" Setting card group """
def __init__(self, title: str, parent=None):
super().__init__(parent=parent)
self.titleLabel = QLabel(title, self)
self.vBoxLayout = QVBoxLayout(self)
self.cardLayout = ExpandLayout()
self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
self.vBoxLayout.setAlignment(Qt.AlignTop)
self.vBoxLayout.setSpacing(0)
self.cardLayout.setContentsMargins(0, 0, 0, 0)
self.cardLayout.setSpacing(2)
self.vBoxLayout.addWidget(self.titleLabel)
self.vBoxLayout.addSpacing(12)
self.vBoxLayout.addLayout(self.cardLayout, 1)
FluentStyleSheet.SETTING_CARD_GROUP.apply(self)
setFont(self.titleLabel, 20)
self.titleLabel.adjustSize()
def addSettingCard(self, card: QWidget):
""" add setting card to group """
card.setParent(self)
self.cardLayout.addWidget(card)
self.adjustSize()
def addSettingCards(self, cards: List[QWidget]):
""" add setting cards to group """
for card in cards:
self.addSettingCard(card)
def adjustSize(self):
h = self.cardLayout.heightForWidth(self.width()) + 46
return self.resize(self.width(), h)