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,6 @@
from .color_dialog import ColorDialog
from .dialog import Dialog, MessageBox
from .folder_list_dialog import FolderListDialog
from .message_dialog import MessageDialog
from .message_box_base import MessageBoxBase
from .mask_dialog_base import MaskDialogBase

View File

@ -0,0 +1,414 @@
# coding:utf-8
from PySide6.QtCore import Qt, Signal, QPoint, QRegularExpression, QSize
from PySide6.QtGui import (QBrush, QColor, QPixmap, QPainter,
QPen, QIntValidator, QRegularExpressionValidator, QIcon)
from PySide6.QtWidgets import QApplication, QLabel, QWidget, QPushButton, QPushButton, QFrame, QVBoxLayout
from ...common.style_sheet import FluentStyleSheet, isDarkTheme
from ..widgets import ClickableSlider, SingleDirectionScrollArea, PushButton, PrimaryPushButton
from ..widgets.line_edit import LineEdit
from .mask_dialog_base import MaskDialogBase
class HuePanel(QWidget):
""" Hue panel """
colorChanged = Signal(QColor)
def __init__(self, color=QColor(255, 0, 0), parent=None):
super().__init__(parent=parent)
self.setFixedSize(256, 256)
self.huePixmap = QPixmap(":/qfluentwidgets/images/color_dialog/HuePanel.png")
self.setColor(color)
def mousePressEvent(self, e):
self.setPickerPosition(e.pos())
def mouseMoveEvent(self, e):
self.setPickerPosition(e.pos())
def setPickerPosition(self, pos):
""" set the position of """
self.pickerPos = pos
self.color.setHsv(
int(max(0, min(1, pos.x() / self.width())) * 360),
int(max(0, min(1, (self.height() - pos.y()) / self.height())) * 255),
255
)
self.update()
self.colorChanged.emit(self.color)
def setColor(self, color):
""" set color """
self.color = QColor(color)
self.color.setHsv(self.color.hue(), self.color.saturation(), 255)
self.pickerPos = QPoint(
int(self.hue/360*self.width()),
int((255 - self.saturation)/255*self.height())
)
self.update()
@property
def hue(self):
return self.color.hue()
@property
def saturation(self):
return self.color.saturation()
def paintEvent(self, e):
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing |
QPainter.SmoothPixmapTransform)
# draw hue panel
painter.setBrush(QBrush(self.huePixmap))
painter.setPen(QPen(QColor(0, 0, 0, 15), 2.4))
painter.drawRoundedRect(self.rect(), 5.6, 5.6)
# draw picker
if self.saturation > 153 or 40 < self.hue < 180:
color = Qt.black
else:
color = QColor(255, 253, 254)
painter.setPen(QPen(color, 3))
painter.setBrush(Qt.NoBrush)
painter.drawEllipse(self.pickerPos.x() - 8,
self.pickerPos.y() - 8, 16, 16)
class BrightnessSlider(ClickableSlider):
""" Brightness slider """
colorChanged = Signal(QColor)
def __init__(self, color, parent=None):
super().__init__(Qt.Horizontal, parent)
self.setRange(0, 255)
self.setSingleStep(1)
self.setColor(color)
self.valueChanged.connect(self.__onValueChanged)
def setColor(self, color):
""" set color """
self.color = QColor(color)
self.setValue(self.color.value())
qss = FluentStyleSheet.COLOR_DIALOG.content()
qss = qss.replace('--slider-hue', str(self.color.hue()))
qss = qss.replace('--slider-saturation', str(self.color.saturation()))
self.setStyleSheet(qss)
def __onValueChanged(self, value):
""" slider value changed slot """
self.color.setHsv(self.color.hue(), self.color.saturation(), value, self.color.alpha())
self.setColor(self.color)
self.colorChanged.emit(self.color)
class ColorCard(QWidget):
""" Color card """
def __init__(self, color, parent=None, enableAlpha=False):
super().__init__(parent)
self.setFixedSize(44, 128)
self.setColor(color)
self.enableAlpha = enableAlpha
self.titledPixmap = self._createTitledBackground()
def _createTitledBackground(self):
pixmap = QPixmap(8, 8)
pixmap.fill(Qt.transparent)
painter = QPainter(pixmap)
c = 255 if isDarkTheme() else 0
color = QColor(c, c, c, 26)
painter.fillRect(4, 0, 4, 4, color)
painter.fillRect(0, 4, 4, 4, color)
painter.end()
return pixmap
def setColor(self, color):
""" set the color of card """
self.color = QColor(color)
self.update()
def paintEvent(self, e):
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing)
# draw tiled background
if self.enableAlpha:
painter.setBrush(QBrush(self.titledPixmap))
painter.setPen(QColor(0, 0, 0, 13))
painter.drawRoundedRect(self.rect(), 4, 4)
# draw color
painter.setBrush(self.color)
painter.setPen(QColor(0, 0, 0, 13))
painter.drawRoundedRect(self.rect(), 4, 4)
class ColorLineEdit(LineEdit):
""" Color line edit """
valueChanged = Signal(str)
def __init__(self, value, parent=None):
super().__init__(parent)
self.setText(str(value))
self.setFixedSize(136, 33)
self.setClearButtonEnabled(True)
self.setValidator(QIntValidator(0, 255, self))
self.textEdited.connect(self._onTextEdited)
def _onTextEdited(self, text):
""" text edited slot """
state = self.validator().validate(text, 0)[0]
if state == QIntValidator.Acceptable:
self.valueChanged.emit(text)
class HexColorLineEdit(ColorLineEdit):
""" Hex color line edit """
def __init__(self, color, parent=None, enableAlpha=False):
self.colorFormat = QColor.HexArgb if enableAlpha else QColor.HexRgb
super().__init__(QColor(color).name(self.colorFormat)[1:], parent)
if enableAlpha:
self.setValidator(QRegularExpressionValidator(QRegularExpression(r'[A-Fa-f0-9]{8}')))
else:
self.setValidator(QRegularExpressionValidator(QRegularExpression(r'[A-Fa-f0-9]{6}')))
self.setTextMargins(4, 0, 33, 0)
self.prefixLabel = QLabel('#', self)
self.prefixLabel.move(7, 2)
self.prefixLabel.setObjectName('prefixLabel')
def setColor(self, color):
""" set color """
self.setText(color.name(self.colorFormat)[1:])
class OpacityLineEdit(ColorLineEdit):
""" Opacity line edit """
def __init__(self, value, parent=None, enableAlpha=False):
super().__init__(int(value/255*100), parent)
self.setValidator(QRegularExpressionValidator(QRegularExpression(r'[0-9][0-9]{0,1}|100')))
self.setTextMargins(4, 0, 33, 0)
self.suffixLabel = QLabel('%', self)
self.suffixLabel.setObjectName('suffixLabel')
self.textChanged.connect(self._adjustSuffixPos)
def showEvent(self, e):
super().showEvent(e)
self._adjustSuffixPos()
def _adjustSuffixPos(self):
x = self.fontMetrics().boundingRect(self.text()).width() + 18
self.suffixLabel.move(x, 2)
class ColorDialog(MaskDialogBase):
""" Color dialog """
colorChanged = Signal(QColor)
def __init__(self, color, title: str, parent=None, enableAlpha=False):
"""
Parameters
----------
color: `QColor` | `GlobalColor` | str
initial color
title: str
the title of dialog
parent: QWidget
parent widget
enableAlpha: bool
whether to enable the alpha channel
"""
super().__init__(parent)
self.enableAlpha = enableAlpha
if not enableAlpha:
color = QColor(color)
color.setAlpha(255)
self.oldColor = QColor(color)
self.color = QColor(color)
self.scrollArea = SingleDirectionScrollArea(self.widget)
self.scrollWidget = QWidget(self.scrollArea)
self.buttonGroup = QFrame(self.widget)
self.yesButton = PrimaryPushButton(self.tr('OK'), self.buttonGroup)
self.cancelButton = QPushButton(self.tr('Cancel'), self.buttonGroup)
self.titleLabel = QLabel(title, self.scrollWidget)
self.huePanel = HuePanel(color, self.scrollWidget)
self.newColorCard = ColorCard(color, self.scrollWidget, enableAlpha)
self.oldColorCard = ColorCard(color, self.scrollWidget, enableAlpha)
self.brightSlider = BrightnessSlider(color, self.scrollWidget)
self.editLabel = QLabel(self.tr('Edit Color'), self.scrollWidget)
self.redLabel = QLabel(self.tr('Red'), self.scrollWidget)
self.blueLabel = QLabel(self.tr('Blue'), self.scrollWidget)
self.greenLabel = QLabel(self.tr('Green'), self.scrollWidget)
self.opacityLabel = QLabel(self.tr('Opacity'), self.scrollWidget)
self.hexLineEdit = HexColorLineEdit(color, self.scrollWidget, enableAlpha)
self.redLineEdit = ColorLineEdit(self.color.red(), self.scrollWidget)
self.greenLineEdit = ColorLineEdit(self.color.green(), self.scrollWidget)
self.blueLineEdit = ColorLineEdit(self.color.blue(), self.scrollWidget)
self.opacityLineEdit = OpacityLineEdit(self.color.alpha(), self.scrollWidget)
self.vBoxLayout = QVBoxLayout(self.widget)
self.__initWidget()
def __initWidget(self):
self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.scrollArea.setViewportMargins(48, 24, 0, 24)
self.scrollArea.setWidget(self.scrollWidget)
self.widget.setMaximumSize(488, 696+40*self.enableAlpha)
self.widget.resize(488, 696+40*self.enableAlpha)
self.scrollWidget.resize(440, 560+40*self.enableAlpha)
self.buttonGroup.setFixedSize(486, 81)
self.yesButton.setFixedWidth(216)
self.cancelButton.setFixedWidth(216)
self.setShadowEffect(60, (0, 10), QColor(0, 0, 0, 80))
self.setMaskColor(QColor(0, 0, 0, 76))
self.__setQss()
self.__initLayout()
self.__connectSignalToSlot()
def __initLayout(self):
self.huePanel.move(0, 46)
self.newColorCard.move(288, 46)
self.oldColorCard.move(288, self.newColorCard.geometry().bottom()+1)
self.brightSlider.move(0, 324)
self.editLabel.move(0, 385)
self.redLineEdit.move(0, 426)
self.greenLineEdit.move(0, 470)
self.blueLineEdit.move(0, 515)
self.redLabel.move(144, 434)
self.greenLabel.move(144, 478)
self.blueLabel.move(144, 524)
self.hexLineEdit.move(196, 381)
if self.enableAlpha:
self.opacityLineEdit.move(0, 560)
self.opacityLabel.move(144, 567)
else:
self.opacityLineEdit.hide()
self.opacityLabel.hide()
self.vBoxLayout.setSpacing(0)
self.vBoxLayout.setAlignment(Qt.AlignTop)
self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
self.vBoxLayout.addWidget(self.scrollArea, 1)
self.vBoxLayout.addWidget(self.buttonGroup, 0, Qt.AlignBottom)
self.yesButton.move(24, 25)
self.cancelButton.move(250, 25)
def __setQss(self):
self.editLabel.setObjectName('editLabel')
self.titleLabel.setObjectName('titleLabel')
self.yesButton.setObjectName('yesButton')
self.cancelButton.setObjectName('cancelButton')
self.buttonGroup.setObjectName('buttonGroup')
FluentStyleSheet.COLOR_DIALOG.apply(self)
self.titleLabel.adjustSize()
self.editLabel.adjustSize()
def setColor(self, color, movePicker=True):
""" set color """
self.color = QColor(color)
self.brightSlider.setColor(color)
self.newColorCard.setColor(color)
self.hexLineEdit.setColor(color)
self.redLineEdit.setText(str(color.red()))
self.blueLineEdit.setText(str(color.blue()))
self.greenLineEdit.setText(str(color.green()))
if movePicker:
self.huePanel.setColor(color)
def __onHueChanged(self, color):
""" hue changed slot """
self.color.setHsv(
color.hue(), color.saturation(), self.color.value(), self.color.alpha())
self.setColor(self.color)
def __onBrightnessChanged(self, color):
""" brightness changed slot """
self.color.setHsv(
self.color.hue(), self.color.saturation(), color.value(), color.alpha())
self.setColor(self.color, False)
def __onRedChanged(self, red):
""" red channel changed slot """
self.color.setRed(int(red))
self.setColor(self.color)
def __onBlueChanged(self, blue):
""" blue channel changed slot """
self.color.setBlue(int(blue))
self.setColor(self.color)
def __onGreenChanged(self, green):
""" green channel changed slot """
self.color.setGreen(int(green))
self.setColor(self.color)
def __onOpacityChanged(self, opacity):
""" opacity channel changed slot """
self.color.setAlpha(int(int(opacity)/100*255))
self.setColor(self.color)
def __onHexColorChanged(self, color):
""" hex color changed slot """
self.color.setNamedColor("#" + color)
self.setColor(self.color)
def __onYesButtonClicked(self):
""" yes button clicked slot """
self.accept()
if self.color != self.oldColor:
self.colorChanged.emit(self.color)
def updateStyle(self):
""" update style sheet """
self.setStyle(QApplication.style())
self.titleLabel.adjustSize()
self.editLabel.adjustSize()
self.redLabel.adjustSize()
self.greenLabel.adjustSize()
self.blueLabel.adjustSize()
self.opacityLabel.adjustSize()
def showEvent(self, e):
self.updateStyle()
super().showEvent(e)
def __connectSignalToSlot(self):
""" connect signal to slot """
self.cancelButton.clicked.connect(self.reject)
self.yesButton.clicked.connect(self.__onYesButtonClicked)
self.huePanel.colorChanged.connect(self.__onHueChanged)
self.brightSlider.colorChanged.connect(self.__onBrightnessChanged)
self.redLineEdit.valueChanged.connect(self.__onRedChanged)
self.blueLineEdit.valueChanged.connect(self.__onBlueChanged)
self.greenLineEdit.valueChanged.connect(self.__onGreenChanged)
self.hexLineEdit.valueChanged.connect(self.__onHexColorChanged)
self.opacityLineEdit.valueChanged.connect(self.__onOpacityChanged)

View File

@ -0,0 +1,167 @@
# coding:utf-8
from PySide6.QtCore import Qt, Signal, QObject, QEvent
from PySide6.QtGui import QColor
from PySide6.QtWidgets import QLabel, QFrame, QVBoxLayout, QHBoxLayout, QPushButton
from qframelesswindow import FramelessDialog
from ...common.auto_wrap import TextWrap
from ...common.style_sheet import FluentStyleSheet
from ..widgets.button import PrimaryPushButton
from ..widgets.label import BodyLabel
from .mask_dialog_base import MaskDialogBase
class Ui_MessageBox:
""" Ui of message box """
yesSignal = Signal()
cancelSignal = Signal()
def __init__(self, *args, **kwargs):
pass
def _setUpUi(self, title, content, parent):
self.content = content
self.titleLabel = QLabel(title, parent)
self.contentLabel = BodyLabel(content, parent)
self.buttonGroup = QFrame(parent)
self.yesButton = PrimaryPushButton(self.tr('OK'), self.buttonGroup)
self.cancelButton = QPushButton(self.tr('Cancel'), self.buttonGroup)
self.vBoxLayout = QVBoxLayout(parent)
self.textLayout = QVBoxLayout()
self.buttonLayout = QHBoxLayout(self.buttonGroup)
self.__initWidget()
def __initWidget(self):
self.__setQss()
self.__initLayout()
# fixes https://github.com/zhiyiYo/PyQt-Fluent-Widgets/issues/19
self.yesButton.setAttribute(Qt.WA_LayoutUsesWidgetRect)
self.cancelButton.setAttribute(Qt.WA_LayoutUsesWidgetRect)
self.yesButton.setFocus()
self.buttonGroup.setFixedHeight(81)
self.contentLabel.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self._adjustText()
self.yesButton.clicked.connect(self.__onYesButtonClicked)
self.cancelButton.clicked.connect(self.__onCancelButtonClicked)
def _adjustText(self):
if self.isWindow():
if self.parent():
w = max(self.titleLabel.width(), self.parent().width())
chars = max(min(w / 9, 140), 30)
else:
chars = 100
else:
w = max(self.titleLabel.width(), self.window().width())
chars = max(min(w / 9, 100), 30)
self.contentLabel.setText(TextWrap.wrap(self.content, chars, False)[0])
def __initLayout(self):
self.vBoxLayout.setSpacing(0)
self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
self.vBoxLayout.addLayout(self.textLayout, 1)
self.vBoxLayout.addWidget(self.buttonGroup, 0, Qt.AlignBottom)
self.vBoxLayout.setSizeConstraint(QVBoxLayout.SetMinimumSize)
self.textLayout.setSpacing(12)
self.textLayout.setContentsMargins(24, 24, 24, 24)
self.textLayout.addWidget(self.titleLabel, 0, Qt.AlignTop)
self.textLayout.addWidget(self.contentLabel, 0, Qt.AlignTop)
self.buttonLayout.setSpacing(12)
self.buttonLayout.setContentsMargins(24, 24, 24, 24)
self.buttonLayout.addWidget(self.yesButton, 1, Qt.AlignVCenter)
self.buttonLayout.addWidget(self.cancelButton, 1, Qt.AlignVCenter)
def __onCancelButtonClicked(self):
self.reject()
self.cancelSignal.emit()
def __onYesButtonClicked(self):
self.accept()
self.yesSignal.emit()
def __setQss(self):
self.titleLabel.setObjectName("titleLabel")
self.contentLabel.setObjectName("contentLabel")
self.buttonGroup.setObjectName('buttonGroup')
self.cancelButton.setObjectName('cancelButton')
FluentStyleSheet.DIALOG.apply(self)
FluentStyleSheet.DIALOG.apply(self.contentLabel)
self.yesButton.adjustSize()
self.cancelButton.adjustSize()
def setContentCopyable(self, isCopyable: bool):
""" set whether the content is copyable """
if isCopyable:
self.contentLabel.setTextInteractionFlags(
Qt.TextInteractionFlag.TextSelectableByMouse)
else:
self.contentLabel.setTextInteractionFlags(
Qt.TextInteractionFlag.NoTextInteraction)
class Dialog(FramelessDialog, Ui_MessageBox):
""" Dialog box """
yesSignal = Signal()
cancelSignal = Signal()
def __init__(self, title: str, content: str, parent=None):
super().__init__(parent=parent)
self._setUpUi(title, content, self)
self.windowTitleLabel = QLabel(title, self)
self.setResizeEnabled(False)
self.resize(240, 192)
self.titleBar.hide()
self.vBoxLayout.insertWidget(0, self.windowTitleLabel, 0, Qt.AlignTop)
self.windowTitleLabel.setObjectName('windowTitleLabel')
FluentStyleSheet.DIALOG.apply(self)
self.setFixedSize(self.size())
def setTitleBarVisible(self, isVisible: bool):
self.windowTitleLabel.setVisible(isVisible)
class MessageBox(MaskDialogBase, Ui_MessageBox):
""" Message box """
yesSignal = Signal()
cancelSignal = Signal()
def __init__(self, title: str, content: str, parent=None):
super().__init__(parent=parent)
self._setUpUi(title, content, self.widget)
self.setShadowEffect(60, (0, 10), QColor(0, 0, 0, 50))
self.setMaskColor(QColor(0, 0, 0, 76))
self._hBoxLayout.removeWidget(self.widget)
self._hBoxLayout.addWidget(self.widget, 1, Qt.AlignCenter)
self.buttonGroup.setMinimumWidth(280)
self.widget.setFixedSize(
max(self.contentLabel.width(), self.titleLabel.width()) + 48,
self.contentLabel.y() + self.contentLabel.height() + 105
)
def eventFilter(self, obj, e: QEvent):
if obj is self.window():
if e.type() == QEvent.Resize:
self._adjustText()
return super().eventFilter(obj, e)

View File

@ -0,0 +1,307 @@
# coding:utf-8
import os
from PySide6.QtCore import Qt, Signal
from PySide6.QtGui import (QBrush, QColor, QFont, QFontMetrics, QMouseEvent,
QPainter, QPen, QPixmap)
from PySide6.QtWidgets import (QApplication, QFileDialog, QHBoxLayout, QLabel,
QVBoxLayout, QWidget, QPushButton)
from ...common.config import isDarkTheme
from ...common.icon import getIconColor
from ...common.style_sheet import FluentStyleSheet
from .dialog import Dialog
from .mask_dialog_base import MaskDialogBase
from ..widgets.scroll_area import SingleDirectionScrollArea
class FolderListDialog(MaskDialogBase):
""" Folder list dialog box """
folderChanged = Signal(list)
def __init__(self, folderPaths: list, title: str, content: str, parent):
super().__init__(parent=parent)
self.title = title
self.content = content
self.__originalPaths = folderPaths
self.folderPaths = folderPaths.copy()
self.vBoxLayout = QVBoxLayout(self.widget)
self.titleLabel = QLabel(title, self.widget)
self.contentLabel = QLabel(content, self.widget)
self.scrollArea = SingleDirectionScrollArea(self.widget)
self.scrollWidget = QWidget(self.scrollArea)
self.completeButton = QPushButton(self.tr('Done'), self.widget)
self.addFolderCard = AddFolderCard(self.scrollWidget)
self.folderCards = [FolderCard(i, self.scrollWidget)
for i in folderPaths]
self.__initWidget()
def __initWidget(self):
""" initialize widgets """
self.__setQss()
w = max(self.titleLabel.width()+48, self.contentLabel.width()+48, 352)
self.widget.setFixedWidth(w)
self.scrollArea.resize(294, 72)
self.scrollWidget.resize(292, 72)
self.scrollArea.setFixedWidth(294)
self.scrollWidget.setFixedWidth(292)
self.scrollArea.setMaximumHeight(400)
self.scrollArea.setViewportMargins(0, 0, 0, 0)
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setWidget(self.scrollWidget)
self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.scrollArea.hScrollBar.setForceHidden(True)
self.__initLayout()
# connect signal to slot
self.addFolderCard.clicked.connect(self.__showFileDialog)
self.completeButton.clicked.connect(self.__onButtonClicked)
for card in self.folderCards:
card.clicked.connect(self.__showDeleteFolderCardDialog)
def __initLayout(self):
""" initialize layout """
self.vBoxLayout.setContentsMargins(24, 24, 24, 24)
self.vBoxLayout.setSizeConstraint(QVBoxLayout.SetFixedSize)
self.vBoxLayout.setAlignment(Qt.AlignTop)
self.vBoxLayout.setSpacing(0)
# labels
layout_1 = QVBoxLayout()
layout_1.setContentsMargins(0, 0, 0, 0)
layout_1.setSpacing(6)
layout_1.addWidget(self.titleLabel, 0, Qt.AlignTop)
layout_1.addWidget(self.contentLabel, 0, Qt.AlignTop)
self.vBoxLayout.addLayout(layout_1, 0)
self.vBoxLayout.addSpacing(12)
# cards
layout_2 = QHBoxLayout()
layout_2.setAlignment(Qt.AlignCenter)
layout_2.setContentsMargins(4, 0, 4, 0)
layout_2.addWidget(self.scrollArea, 0, Qt.AlignCenter)
self.vBoxLayout.addLayout(layout_2, 1)
self.vBoxLayout.addSpacing(24)
self.scrollLayout = QVBoxLayout(self.scrollWidget)
self.scrollLayout.setAlignment(Qt.AlignTop)
self.scrollLayout.setContentsMargins(0, 0, 0, 0)
self.scrollLayout.setSpacing(8)
self.scrollLayout.addWidget(self.addFolderCard, 0, Qt.AlignTop)
for card in self.folderCards:
self.scrollLayout.addWidget(card, 0, Qt.AlignTop)
# buttons
layout_3 = QHBoxLayout()
layout_3.setContentsMargins(0, 0, 0, 0)
layout_3.addStretch(1)
layout_3.addWidget(self.completeButton)
self.vBoxLayout.addLayout(layout_3, 0)
self.__adjustWidgetSize()
def __showFileDialog(self):
""" show file dialog to select folder """
path = QFileDialog.getExistingDirectory(
self, self.tr("Choose folder"), "./")
if not path or path in self.folderPaths:
return
# create folder card
card = FolderCard(path, self.scrollWidget)
self.scrollLayout.addWidget(card, 0, Qt.AlignTop)
card.clicked.connect(self.__showDeleteFolderCardDialog)
card.show()
self.folderPaths.append(path)
self.folderCards.append(card)
self.__adjustWidgetSize()
def __showDeleteFolderCardDialog(self):
""" show delete folder card dialog """
sender = self.sender()
title = self.tr('Are you sure you want to delete the folder?')
content = self.tr("If you delete the ") + f'"{sender.folderName}"' + \
self.tr(" folder and remove it from the list, the folder will no "
"longer appear in the list, but will not be deleted.")
dialog = Dialog(title, content, self.window())
dialog.yesSignal.connect(lambda: self.__deleteFolderCard(sender))
dialog.exec_()
def __deleteFolderCard(self, folderCard):
""" delete selected folder card """
self.scrollLayout.removeWidget(folderCard)
index = self.folderCards.index(folderCard)
self.folderCards.pop(index)
self.folderPaths.pop(index)
folderCard.deleteLater()
# adjust height
self.__adjustWidgetSize()
def __setQss(self):
""" set style sheet """
self.titleLabel.setObjectName('titleLabel')
self.contentLabel.setObjectName('contentLabel')
self.completeButton.setObjectName('completeButton')
self.scrollWidget.setObjectName('scrollWidget')
FluentStyleSheet.FOLDER_LIST_DIALOG.apply(self)
self.setStyle(QApplication.style())
self.titleLabel.adjustSize()
self.contentLabel.adjustSize()
self.completeButton.adjustSize()
def __onButtonClicked(self):
""" done button clicked slot """
if sorted(self.__originalPaths) != sorted(self.folderPaths):
self.setEnabled(False)
QApplication.processEvents()
self.folderChanged.emit(self.folderPaths)
self.close()
def __adjustWidgetSize(self):
N = len(self.folderCards)
h = 72*(N+1) + 8*N
self.scrollArea.setFixedHeight(min(h, 400))
class ClickableWindow(QWidget):
""" Clickable window """
clicked = Signal()
def __init__(self, parent=None):
super().__init__(parent)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setWindowFlags(Qt.FramelessWindowHint)
self.setFixedSize(292, 72)
self._isPressed = None
self._isEnter = False
def enterEvent(self, e):
self._isEnter = True
self.update()
def leaveEvent(self, e):
self._isEnter = False
self.update()
def mouseReleaseEvent(self, e):
self._isPressed = False
self.update()
if e.button() == Qt.LeftButton:
self.clicked.emit()
def mousePressEvent(self, e: QMouseEvent):
self._isPressed = True
self.update()
def paintEvent(self, e):
""" paint window """
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing)
isDark = isDarkTheme()
bg = 51 if isDark else 204
brush = QBrush(QColor(bg, bg, bg))
painter.setPen(Qt.NoPen)
if not self._isEnter:
painter.setBrush(brush)
painter.drawRoundedRect(self.rect(), 4, 4)
else:
painter.setPen(QPen(QColor(bg, bg, bg), 2))
painter.drawRect(1, 1, self.width() - 2, self.height() - 2)
painter.setPen(Qt.NoPen)
if not self._isPressed:
bg = 24 if isDark else 230
brush.setColor(QColor(bg, bg, bg))
painter.setBrush(brush)
painter.drawRect(2, 2, self.width() - 4, self.height() - 4)
else:
bg = 102 if isDark else 230
brush.setColor(QColor(153, 153, 153))
painter.setBrush(brush)
painter.drawRoundedRect(
5, 1, self.width() - 10, self.height() - 2, 2, 2)
class FolderCard(ClickableWindow):
""" Folder card """
def __init__(self, folderPath: str, parent=None):
super().__init__(parent)
self.folderPath = folderPath
self.folderName = os.path.basename(folderPath)
c = getIconColor()
self.__closeIcon = QPixmap(f":/qfluentwidgets/images/folder_list_dialog/Close_{c}.png").scaled(
12, 12, Qt.KeepAspectRatio, Qt.SmoothTransformation)
def paintEvent(self, e):
""" paint card """
super().paintEvent(e)
painter = QPainter(self)
painter.setRenderHints(
QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform | QPainter.Antialiasing)
# paint text and icon
color = Qt.white if isDarkTheme() else Qt.black
painter.setPen(color)
if self._isPressed:
self.__drawText(painter, 12, 12, 12, 10)
painter.drawPixmap(self.width() - 26, 18, self.__closeIcon)
else:
self.__drawText(painter, 10, 13, 10, 11)
painter.drawPixmap(self.width() - 24, 20, self.__closeIcon)
def __drawText(self, painter, x1, fontSize1, x2, fontSize2):
""" draw text """
# paint folder name
font = QFont("Microsoft YaHei")
font.setBold(True)
font.setPixelSize(fontSize1)
painter.setFont(font)
name = QFontMetrics(font).elidedText(
self.folderName, Qt.ElideRight, self.width()-48)
painter.drawText(x1, 30, name)
# paint folder path
font = QFont("Microsoft YaHei")
font.setPixelSize(fontSize2)
painter.setFont(font)
path = QFontMetrics(font).elidedText(
self.folderPath, Qt.ElideRight, self.width()-24)
painter.drawText(x2, 37, self.width() - 16, 18, Qt.AlignLeft, path)
class AddFolderCard(ClickableWindow):
""" Add folder card """
def __init__(self, parent=None):
super().__init__(parent)
c = getIconColor()
self.__iconPix = QPixmap(f":/qfluentwidgets/images/folder_list_dialog/Add_{c}.png").scaled(
22, 22, Qt.KeepAspectRatio, Qt.SmoothTransformation)
def paintEvent(self, e):
""" paint card """
super().paintEvent(e)
painter = QPainter(self)
w = self.width()
h = self.height()
pw = self.__iconPix.width()
ph = self.__iconPix.height()
if not self._isPressed:
painter.drawPixmap(
int(w/2 - pw/2), int(h/2 - ph/2), self.__iconPix)
else:
painter.drawPixmap(
int(w/2 - (pw - 4)/2), int(h/2 - (ph - 4)/2), pw - 4, ph - 4, self.__iconPix)

View File

@ -0,0 +1,120 @@
# coding:utf-8
from PySide6.QtCore import QEasingCurve, QPropertyAnimation, Qt, QEvent, QPoint
from PySide6.QtGui import QColor, QResizeEvent
from PySide6.QtWidgets import (QDialog, QGraphicsDropShadowEffect,
QGraphicsOpacityEffect, QHBoxLayout, QWidget, QFrame)
from ...common.config import isDarkTheme
class MaskDialogBase(QDialog):
""" Dialog box base class with a mask """
def __init__(self, parent=None):
super().__init__(parent=parent)
self._isClosableOnMaskClicked = False
self._isDraggable = False
self._dragPos = QPoint()
self._hBoxLayout = QHBoxLayout(self)
self.windowMask = QWidget(self)
# dialog box in the center of mask, all widgets take it as parent
self.widget = QFrame(self, objectName='centerWidget')
self.setWindowFlags(Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setGeometry(0, 0, parent.width(), parent.height())
c = 0 if isDarkTheme() else 255
self.windowMask.resize(self.size())
self.windowMask.setStyleSheet(f'background:rgba({c}, {c}, {c}, 0.6)')
self._hBoxLayout.addWidget(self.widget)
self.setShadowEffect()
self.window().installEventFilter(self)
self.windowMask.installEventFilter(self)
self.widget.installEventFilter(self)
def setShadowEffect(self, blurRadius=60, offset=(0, 10), color=QColor(0, 0, 0, 100)):
""" add shadow to dialog """
shadowEffect = QGraphicsDropShadowEffect(self.widget)
shadowEffect.setBlurRadius(blurRadius)
shadowEffect.setOffset(*offset)
shadowEffect.setColor(color)
self.widget.setGraphicsEffect(None)
self.widget.setGraphicsEffect(shadowEffect)
def setMaskColor(self, color: QColor):
""" set the color of mask """
self.windowMask.setStyleSheet(f"""
background: rgba({color.red()}, {color.green()}, {color.blue()}, {color.alpha()})
""")
def showEvent(self, e):
""" fade in """
opacityEffect = QGraphicsOpacityEffect(self)
self.setGraphicsEffect(opacityEffect)
opacityAni = QPropertyAnimation(opacityEffect, b'opacity', self)
opacityAni.setStartValue(0)
opacityAni.setEndValue(1)
opacityAni.setDuration(200)
opacityAni.setEasingCurve(QEasingCurve.InSine)
opacityAni.finished.connect(lambda: self.setGraphicsEffect(None))
opacityAni.start()
super().showEvent(e)
def done(self, code):
""" fade out """
self.widget.setGraphicsEffect(None)
opacityEffect = QGraphicsOpacityEffect(self)
self.setGraphicsEffect(opacityEffect)
opacityAni = QPropertyAnimation(opacityEffect, b'opacity', self)
opacityAni.setStartValue(1)
opacityAni.setEndValue(0)
opacityAni.setDuration(100)
opacityAni.finished.connect(lambda: self._onDone(code))
opacityAni.start()
def _onDone(self, code):
self.setGraphicsEffect(None)
QDialog.done(self, code)
def isClosableOnMaskClicked(self):
return self._isClosableOnMaskClicked
def setClosableOnMaskClicked(self, isClosable: bool):
self._isClosableOnMaskClicked = isClosable
def setDraggable(self, draggable: bool):
self._isDraggable = draggable
def isDraggable(self) -> bool:
return self._isDraggable
def resizeEvent(self, e):
self.windowMask.resize(self.size())
def eventFilter(self, obj, e: QEvent):
if obj is self.window():
if e.type() == QEvent.Resize:
re = QResizeEvent(e)
self.resize(re.size())
elif obj is self.windowMask:
if e.type() == QEvent.MouseButtonRelease and e.button() == Qt.LeftButton \
and self.isClosableOnMaskClicked():
self.reject()
elif obj is self.widget and self.isDraggable():
if e.type() == QEvent.MouseButtonPress and e.button() == Qt.LeftButton:
if not self.widget.childrenRegion().contains(e.pos()):
self._dragPos = e.pos()
return True
elif e.type() == QEvent.MouseMove and not self._dragPos.isNull():
pos = self.widget.pos() + e.pos() - self._dragPos
pos.setX(max(0, min(pos.x(), self.width() - self.widget.width())))
pos.setY(max(0, min(pos.y(), self.height() - self.widget.height())))
self.widget.move(pos)
return True
elif e.type() == QEvent.MouseButtonRelease:
self._dragPos = QPoint()
return super().eventFilter(obj, e)

View File

@ -0,0 +1,92 @@
# coding:utf-8
from PySide6.QtCore import Qt, Signal
from PySide6.QtGui import QColor
from PySide6.QtWidgets import QFrame, QVBoxLayout, QHBoxLayout, QPushButton
from ...common.style_sheet import FluentStyleSheet
from ..widgets.button import PrimaryPushButton
from .mask_dialog_base import MaskDialogBase
class MessageBoxBase(MaskDialogBase):
""" Message box base """
def __init__(self, parent=None):
super().__init__(parent=parent)
self.buttonGroup = QFrame(self.widget)
self.yesButton = PrimaryPushButton(self.tr('OK'), self.buttonGroup)
self.cancelButton = QPushButton(self.tr('Cancel'), self.buttonGroup)
self.vBoxLayout = QVBoxLayout(self.widget)
self.viewLayout = QVBoxLayout()
self.buttonLayout = QHBoxLayout(self.buttonGroup)
self.__initWidget()
def __initWidget(self):
self.__setQss()
self.__initLayout()
self.setShadowEffect(60, (0, 10), QColor(0, 0, 0, 50))
self.setMaskColor(QColor(0, 0, 0, 76))
# fixes https://github.com/zhiyiYo/PyQt-Fluent-Widgets/issues/19
self.yesButton.setAttribute(Qt.WA_LayoutUsesWidgetRect)
self.cancelButton.setAttribute(Qt.WA_LayoutUsesWidgetRect)
self.yesButton.setAttribute(Qt.WA_MacShowFocusRect, False)
self.yesButton.setFocus()
self.buttonGroup.setFixedHeight(81)
self.yesButton.clicked.connect(self.__onYesButtonClicked)
self.cancelButton.clicked.connect(self.__onCancelButtonClicked)
def __initLayout(self):
self._hBoxLayout.removeWidget(self.widget)
self._hBoxLayout.addWidget(self.widget, 1, Qt.AlignCenter)
self.vBoxLayout.setSpacing(0)
self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
self.vBoxLayout.addLayout(self.viewLayout, 1)
self.vBoxLayout.addWidget(self.buttonGroup, 0, Qt.AlignBottom)
self.viewLayout.setSpacing(12)
self.viewLayout.setContentsMargins(24, 24, 24, 24)
self.buttonLayout.setSpacing(12)
self.buttonLayout.setContentsMargins(24, 24, 24, 24)
self.buttonLayout.addWidget(self.yesButton, 1, Qt.AlignVCenter)
self.buttonLayout.addWidget(self.cancelButton, 1, Qt.AlignVCenter)
def validate(self) -> bool:
""" validate the data of form before closing dialog
Returns
-------
isValid: bool
whether the data of form is legal
"""
return True
def __onCancelButtonClicked(self):
self.reject()
def __onYesButtonClicked(self):
if self.validate():
self.accept()
def __setQss(self):
self.buttonGroup.setObjectName('buttonGroup')
self.cancelButton.setObjectName('cancelButton')
FluentStyleSheet.DIALOG.apply(self)
def hideYesButton(self):
self.yesButton.hide()
self.buttonLayout.insertStretch(0, 1)
def hideCancelButton(self):
self.cancelButton.hide()
self.buttonLayout.insertStretch(0, 1)

View File

@ -0,0 +1,65 @@
# coding:utf-8
from PySide6.QtCore import Signal
from PySide6.QtWidgets import QLabel, QPushButton, QDialog
from ...common.auto_wrap import TextWrap
from ...common.style_sheet import FluentStyleSheet
from .mask_dialog_base import MaskDialogBase
class MessageDialog(MaskDialogBase):
""" Win10 style message dialog box with a mask """
yesSignal = Signal()
cancelSignal = Signal()
def __init__(self, title: str, content: str, parent):
super().__init__(parent=parent)
self.content = content
self.titleLabel = QLabel(title, self.widget)
self.contentLabel = QLabel(content, self.widget)
self.yesButton = QPushButton(self.tr('OK'), self.widget)
self.cancelButton = QPushButton(self.tr('Cancel'), self.widget)
self.__initWidget()
def __initWidget(self):
""" initialize widgets """
self.windowMask.resize(self.size())
self.widget.setMaximumWidth(540)
self.titleLabel.move(24, 24)
self.contentLabel.move(24, 56)
self.contentLabel.setText(TextWrap.wrap(self.content, 71)[0])
self.__setQss()
self.__initLayout()
# connect signal to slot
self.yesButton.clicked.connect(self.__onYesButtonClicked)
self.cancelButton.clicked.connect(self.__onCancelButtonClicked)
def __initLayout(self):
""" initialize layout """
self.contentLabel.adjustSize()
self.widget.setFixedSize(48+self.contentLabel.width(),
self.contentLabel.y() + self.contentLabel.height()+92)
self.yesButton.resize((self.widget.width() - 54) // 2, 32)
self.cancelButton.resize(self.yesButton.width(), 32)
self.yesButton.move(24, self.widget.height()-56)
self.cancelButton.move(
self.widget.width()-24-self.cancelButton.width(), self.widget.height()-56)
def __onCancelButtonClicked(self):
self.cancelSignal.emit()
self.reject()
def __onYesButtonClicked(self):
self.setEnabled(False)
self.yesSignal.emit()
self.accept()
def __setQss(self):
""" set style sheet """
self.windowMask.setObjectName('windowMask')
self.titleLabel.setObjectName('titleLabel')
self.contentLabel.setObjectName('contentLabel')
FluentStyleSheet.MESSAGE_DIALOG.apply(self)