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,514 @@
# coding:utf-8
import sys
from pathlib import Path
from PySide6.QtCore import Qt, QPoint, QSize, QUrl, QRect, QPropertyAnimation
from PySide6.QtGui import QIcon, QFont, QColor, QPainter
from PySide6.QtWidgets import QApplication, QWidget, QHBoxLayout, QVBoxLayout, QGraphicsOpacityEffect
from qfluentwidgets import (CardWidget, setTheme, Theme, IconWidget, BodyLabel, CaptionLabel, PushButton,
TransparentToolButton, FluentIcon, RoundMenu, Action, ElevatedCardWidget,
ImageLabel, isDarkTheme, FlowLayout, MSFluentTitleBar, SimpleCardWidget,
HeaderCardWidget, InfoBarIcon, HyperlinkLabel, HorizontalFlipView,
PrimaryPushButton, TitleLabel, PillPushButton, setFont, ScrollArea,
VerticalSeparator, MSFluentWindow, NavigationItemPosition, GroupHeaderCardWidget,
ComboBox, SearchLineEdit)
from qfluentwidgets.components.widgets.acrylic_label import AcrylicBrush
def isWin11():
return sys.platform == 'win32' and sys.getwindowsversion().build >= 22000
if isWin11():
from qframelesswindow import AcrylicWindow as Window
else:
from qframelesswindow import FramelessWindow as Window
class AppCard(CardWidget):
""" App card """
def __init__(self, icon, title, content, parent=None):
super().__init__(parent)
self.iconWidget = IconWidget(icon)
self.titleLabel = BodyLabel(title, self)
self.contentLabel = CaptionLabel(content, self)
self.openButton = PushButton('打开', self)
self.moreButton = TransparentToolButton(FluentIcon.MORE, self)
self.hBoxLayout = QHBoxLayout(self)
self.vBoxLayout = QVBoxLayout()
self.setFixedHeight(73)
self.iconWidget.setFixedSize(48, 48)
self.contentLabel.setTextColor("#606060", "#d2d2d2")
self.openButton.setFixedWidth(120)
self.hBoxLayout.setContentsMargins(20, 11, 11, 11)
self.hBoxLayout.setSpacing(15)
self.hBoxLayout.addWidget(self.iconWidget)
self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
self.vBoxLayout.setSpacing(0)
self.vBoxLayout.addWidget(self.titleLabel, 0, Qt.AlignVCenter)
self.vBoxLayout.addWidget(self.contentLabel, 0, Qt.AlignVCenter)
self.vBoxLayout.setAlignment(Qt.AlignVCenter)
self.hBoxLayout.addLayout(self.vBoxLayout)
self.hBoxLayout.addStretch(1)
self.hBoxLayout.addWidget(self.openButton, 0, Qt.AlignRight)
self.hBoxLayout.addWidget(self.moreButton, 0, Qt.AlignRight)
self.moreButton.setFixedSize(32, 32)
self.moreButton.clicked.connect(self.onMoreButtonClicked)
def onMoreButtonClicked(self):
menu = RoundMenu(parent=self)
menu.addAction(Action(FluentIcon.SHARE, '共享', self))
menu.addAction(Action(FluentIcon.CHAT, '写评论', self))
menu.addAction(Action(FluentIcon.PIN, '固定到任务栏', self))
x = (self.moreButton.width() - menu.width()) // 2 + 10
pos = self.moreButton.mapToGlobal(QPoint(x, self.moreButton.height()))
menu.exec(pos)
class EmojiCard(ElevatedCardWidget):
""" Emoji card """
def __init__(self, iconPath: str, parent=None):
super().__init__(parent)
self.iconWidget = ImageLabel(iconPath, self)
self.label = CaptionLabel(Path(iconPath).stem, self)
self.iconWidget.scaledToHeight(68)
self.vBoxLayout = QVBoxLayout(self)
self.vBoxLayout.setAlignment(Qt.AlignCenter)
self.vBoxLayout.addStretch(1)
self.vBoxLayout.addWidget(self.iconWidget, 0, Qt.AlignCenter)
self.vBoxLayout.addStretch(1)
self.vBoxLayout.addWidget(
self.label, 0, Qt.AlignHCenter | Qt.AlignBottom)
self.setFixedSize(168, 176)
class StatisticsWidget(QWidget):
""" Statistics widget """
def __init__(self, title: str, value: str, parent=None):
super().__init__(parent=parent)
self.titleLabel = CaptionLabel(title, self)
self.valueLabel = BodyLabel(value, self)
self.vBoxLayout = QVBoxLayout(self)
self.vBoxLayout.setContentsMargins(16, 0, 16, 0)
self.vBoxLayout.addWidget(self.valueLabel, 0, Qt.AlignTop)
self.vBoxLayout.addWidget(self.titleLabel, 0, Qt.AlignBottom)
setFont(self.valueLabel, 18, QFont.DemiBold)
self.titleLabel.setTextColor(QColor(96, 96, 96), QColor(206, 206, 206))
class AppInfoCard(SimpleCardWidget):
""" App information card """
def __init__(self, parent=None):
super().__init__(parent)
self.iconLabel = ImageLabel(":/qfluentwidgets/images/logo.png", self)
self.iconLabel.setBorderRadius(8, 8, 8, 8)
self.iconLabel.scaledToWidth(120)
self.nameLabel = TitleLabel('QFluentWidgets', self)
self.installButton = PrimaryPushButton('安装', self)
self.companyLabel = HyperlinkLabel(
QUrl('https://qfluentwidgets.com'), 'Shokokawaii Inc.', self)
self.installButton.setFixedWidth(160)
self.scoreWidget = StatisticsWidget('平均', '5.0', self)
self.separator = VerticalSeparator(self)
self.commentWidget = StatisticsWidget('评论数', '3K', self)
self.descriptionLabel = BodyLabel(
'PyQt-Fluent-Widgets 是一个基于 PyQt/PySide 的 Fluent Design 风格组件库,包含许多美观实用的组件,支持亮暗主题无缝切换和自定义主题色,帮助开发者快速实现美观优雅的现代化界面。', self)
self.descriptionLabel.setWordWrap(True)
self.tagButton = PillPushButton('组件库', self)
self.tagButton.setCheckable(False)
setFont(self.tagButton, 12)
self.tagButton.setFixedSize(80, 32)
self.shareButton = TransparentToolButton(FluentIcon.SHARE, self)
self.shareButton.setFixedSize(32, 32)
self.shareButton.setIconSize(QSize(14, 14))
self.hBoxLayout = QHBoxLayout(self)
self.vBoxLayout = QVBoxLayout()
self.topLayout = QHBoxLayout()
self.statisticsLayout = QHBoxLayout()
self.buttonLayout = QHBoxLayout()
self.initLayout()
self.setBorderRadius(8)
def initLayout(self):
self.hBoxLayout.setSpacing(30)
self.hBoxLayout.setContentsMargins(34, 24, 24, 24)
self.hBoxLayout.addWidget(self.iconLabel)
self.hBoxLayout.addLayout(self.vBoxLayout)
self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
self.vBoxLayout.setSpacing(0)
# name label and install button
self.vBoxLayout.addLayout(self.topLayout)
self.topLayout.setContentsMargins(0, 0, 0, 0)
self.topLayout.addWidget(self.nameLabel)
self.topLayout.addWidget(self.installButton, 0, Qt.AlignRight)
# company label
self.vBoxLayout.addSpacing(3)
self.vBoxLayout.addWidget(self.companyLabel)
# statistics widgets
self.vBoxLayout.addSpacing(20)
self.vBoxLayout.addLayout(self.statisticsLayout)
self.statisticsLayout.setContentsMargins(0, 0, 0, 0)
self.statisticsLayout.setSpacing(10)
self.statisticsLayout.addWidget(self.scoreWidget)
self.statisticsLayout.addWidget(self.separator)
self.statisticsLayout.addWidget(self.commentWidget)
self.statisticsLayout.setAlignment(Qt.AlignLeft)
# description label
self.vBoxLayout.addSpacing(20)
self.vBoxLayout.addWidget(self.descriptionLabel)
# button
self.vBoxLayout.addSpacing(12)
self.buttonLayout.setContentsMargins(0, 0, 0, 0)
self.vBoxLayout.addLayout(self.buttonLayout)
self.buttonLayout.addWidget(self.tagButton, 0, Qt.AlignLeft)
self.buttonLayout.addWidget(self.shareButton, 0, Qt.AlignRight)
class GalleryCard(HeaderCardWidget):
""" Gallery card """
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle('屏幕截图')
self.setBorderRadius(8)
self.flipView = HorizontalFlipView(self)
self.expandButton = TransparentToolButton(
FluentIcon.CHEVRON_RIGHT_MED, self)
self.expandButton.setFixedSize(32, 32)
self.expandButton.setIconSize(QSize(12, 12))
self.flipView.addImages([
'resource/shoko1.jpg', 'resource/shoko2.jpg',
'resource/shoko3.jpg', 'resource/shoko4.jpg',
])
self.flipView.setBorderRadius(8)
self.flipView.setSpacing(10)
self.headerLayout.addWidget(self.expandButton, 0, Qt.AlignRight)
self.viewLayout.addWidget(self.flipView)
class DescriptionCard(HeaderCardWidget):
""" Description card """
def __init__(self, parent=None):
super().__init__(parent)
self.descriptionLabel = BodyLabel(
'PyQt-Fluent-Widgets 是一个基于 PyQt/PySide 的 Fluent Design 风格组件库,包含许多美观实用的组件,支持亮暗主题无缝切换和自定义主题色,搭配所见即所得的 QtDesigner帮助开发者快速实现美观优雅的现代化界面。', self)
self.descriptionLabel.setWordWrap(True)
self.viewLayout.addWidget(self.descriptionLabel)
self.setTitle('描述')
self.setBorderRadius(8)
class SystemRequirementCard(HeaderCardWidget):
""" System requirements card """
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle('系统要求')
self.setBorderRadius(8)
self.infoLabel = BodyLabel('此产品适用于你的设备。具有复选标记的项目符合开发人员的系统要求。', self)
self.successIcon = IconWidget(InfoBarIcon.SUCCESS, self)
self.detailButton = HyperlinkLabel('详细信息', self)
self.vBoxLayout = QVBoxLayout()
self.hBoxLayout = QHBoxLayout()
self.successIcon.setFixedSize(16, 16)
self.hBoxLayout.setSpacing(10)
self.vBoxLayout.setSpacing(16)
self.hBoxLayout.setContentsMargins(0, 0, 0, 0)
self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
self.hBoxLayout.addWidget(self.successIcon)
self.hBoxLayout.addWidget(self.infoLabel)
self.vBoxLayout.addLayout(self.hBoxLayout)
self.vBoxLayout.addWidget(self.detailButton)
self.viewLayout.addLayout(self.vBoxLayout)
class SettinsCard(GroupHeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("基本设置")
self.setBorderRadius(8)
self.chooseButton = PushButton("选择")
self.comboBox = ComboBox()
self.lineEdit = SearchLineEdit()
self.chooseButton.setFixedWidth(120)
self.lineEdit.setFixedWidth(320)
self.comboBox.setFixedWidth(320)
self.comboBox.addItems(["始终显示(首次打包时建议启用)", "始终隐藏"])
self.lineEdit.setPlaceholderText("输入入口脚本的路径")
self.addGroup("resource/Rocket.svg", "构建目录", "选择 Nuitka 的输出目录", self.chooseButton)
self.addGroup("resource/Joystick.svg", "运行终端", "设置是否显示命令行终端", self.comboBox)
self.addGroup("resource/Python.svg", "入口脚本", "选择软件的入口脚本", self.lineEdit)
class LightBox(QWidget):
""" Light box """
def __init__(self, parent=None):
super().__init__(parent=parent)
if isDarkTheme():
tintColor = QColor(32, 32, 32, 200)
else:
tintColor = QColor(255, 255, 255, 160)
self.acrylicBrush = AcrylicBrush(self, 30, tintColor, QColor(0, 0, 0, 0))
self.opacityEffect = QGraphicsOpacityEffect(self)
self.opacityAni = QPropertyAnimation(self.opacityEffect, b"opacity", self)
self.opacityEffect.setOpacity(1)
self.setGraphicsEffect(self.opacityEffect)
self.vBoxLayout = QVBoxLayout(self)
self.closeButton = TransparentToolButton(FluentIcon.CLOSE, self)
self.flipView = HorizontalFlipView(self)
self.nameLabel = BodyLabel('屏幕截图 1', self)
self.pageNumButton = PillPushButton('1 / 4', self)
self.pageNumButton.setCheckable(False)
self.pageNumButton.setFixedSize(80, 32)
setFont(self.nameLabel, 16, QFont.DemiBold)
self.closeButton.setFixedSize(32, 32)
self.closeButton.setIconSize(QSize(14, 14))
self.closeButton.clicked.connect(self.fadeOut)
self.vBoxLayout.setContentsMargins(26, 28, 26, 28)
self.vBoxLayout.addWidget(self.closeButton, 0, Qt.AlignRight | Qt.AlignTop)
self.vBoxLayout.addWidget(self.flipView, 1)
self.vBoxLayout.addWidget(self.nameLabel, 0, Qt.AlignHCenter)
self.vBoxLayout.addSpacing(10)
self.vBoxLayout.addWidget(self.pageNumButton, 0, Qt.AlignHCenter)
self.flipView.addImages([
'resource/shoko1.jpg', 'resource/shoko2.jpg',
'resource/shoko3.jpg', 'resource/shoko4.jpg',
])
self.flipView.currentIndexChanged.connect(self.setCurrentIndex)
def setCurrentIndex(self, index: int):
self.nameLabel.setText(f'屏幕截图 {index + 1}')
self.pageNumButton.setText(f'{index + 1} / {self.flipView.count()}')
self.flipView.setCurrentIndex(index)
def paintEvent(self, e):
if self.acrylicBrush.isAvailable():
return self.acrylicBrush.paint()
painter = QPainter(self)
painter.setPen(Qt.NoPen)
if isDarkTheme():
painter.setBrush(QColor(32, 32, 32))
else:
painter.setBrush(QColor(255, 255, 255))
painter.drawRect(self.rect())
def resizeEvent(self, e):
w = self.width() - 52
self.flipView.setItemSize(QSize(w, w * 9 // 16))
def fadeIn(self):
rect = QRect(self.mapToGlobal(QPoint()), self.size())
self.acrylicBrush.grabImage(rect)
self.opacityAni.setStartValue(0)
self.opacityAni.setEndValue(1)
self.opacityAni.setDuration(150)
self.opacityAni.start()
self.show()
def fadeOut(self):
self.opacityAni.setStartValue(1)
self.opacityAni.setEndValue(0)
self.opacityAni.setDuration(150)
self.opacityAni.finished.connect(self._onAniFinished)
self.opacityAni.start()
def _onAniFinished(self):
self.opacityAni.finished.disconnect()
self.hide()
class MicaWindow(Window):
def __init__(self):
super().__init__()
self.setTitleBar(MSFluentTitleBar(self))
if isWin11():
self.windowEffect.setMicaEffect(self.winId(), isDarkTheme())
class Demo1(MicaWindow):
def __init__(self):
super().__init__()
self.setWindowIcon(QIcon(':/qfluentwidgets/images/logo.png'))
self.setWindowTitle('Fluent Emoji gallery')
self.flowLayout = FlowLayout(self)
self.resize(580, 680)
self.flowLayout.setSpacing(6)
self.flowLayout.setContentsMargins(30, 60, 30, 30)
self.flowLayout.setAlignment(Qt.AlignVCenter)
for path in Path('./resource').glob('*.png'):
self.addCard(str(path))
def addCard(self, iconPath: str):
card = EmojiCard(iconPath, self)
self.flowLayout.addWidget(card)
class Demo2(MicaWindow):
def __init__(self):
super().__init__()
self.resize(600, 600)
self.vBoxLayout = QVBoxLayout(self)
self.vBoxLayout.setSpacing(6)
self.vBoxLayout.setContentsMargins(30, 60, 30, 30)
self.vBoxLayout.setAlignment(Qt.AlignTop)
suffix = ":/qfluentwidgets/images/controls"
self.addCard(f":/qfluentwidgets/images/logo.png",
"PyQt-Fluent-Widgets", 'Shokokawaii Inc.')
self.addCard(f"{suffix}/TitleBar.png",
"PyQt-Frameless-Window", 'Shokokawaii Inc.')
self.addCard(f"{suffix}/RatingControl.png",
"反馈中心", 'Microsoft Corporation')
self.addCard(f"{suffix}/Checkbox.png",
"Microsoft 使用技巧", 'Microsoft Corporation')
self.addCard(f"{suffix}/Pivot.png", "MSN 天气", 'Microsoft Corporation')
self.addCard(f"{suffix}/MediaPlayerElement.png",
"电影和电视", 'Microsoft Corporation')
self.addCard(f"{suffix}/PersonPicture.png",
"照片", 'Microsoft Corporation')
def addCard(self, icon, title, content):
card = AppCard(icon, title, content, self)
self.vBoxLayout.addWidget(card, alignment=Qt.AlignTop)
class AppInterface(ScrollArea):
def __init__(self, parent=None):
super().__init__(parent)
self.view = QWidget(self)
self.vBoxLayout = QVBoxLayout(self.view)
self.appCard = AppInfoCard(self)
self.galleryCard = GalleryCard(self)
self.descriptionCard = DescriptionCard(self)
self.settingCard = SettinsCard(self)
self.systemCard = SystemRequirementCard(self)
self.lightBox = LightBox(self)
self.lightBox.hide()
self.galleryCard.flipView.itemClicked.connect(self.showLightBox)
self.setWidget(self.view)
self.setWidgetResizable(True)
self.setObjectName("appInterface")
self.vBoxLayout.setSpacing(10)
self.vBoxLayout.setContentsMargins(0, 0, 10, 30)
self.vBoxLayout.addWidget(self.appCard, 0, Qt.AlignTop)
self.vBoxLayout.addWidget(self.galleryCard, 0, Qt.AlignTop)
self.vBoxLayout.addWidget(self.settingCard, 0, Qt.AlignTop)
self.vBoxLayout.addWidget(self.descriptionCard, 0, Qt.AlignTop)
self.vBoxLayout.addWidget(self.systemCard, 0, Qt.AlignTop)
self.enableTransparentBackground()
def showLightBox(self):
index = self.galleryCard.flipView.currentIndex()
self.lightBox.setCurrentIndex(index)
self.lightBox.fadeIn()
def resizeEvent(self, e):
super().resizeEvent(e)
self.lightBox.resize(self.size())
class Demo3(MSFluentWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.appInterface = AppInterface(self)
# add sub interfaces
self.addSubInterface(self.appInterface, FluentIcon.LIBRARY, "", FluentIcon.LIBRARY_FILL, isTransparent=True)
self.navigationInterface.addItem("editInterface", FluentIcon.EDIT, "编辑", selectable=False)
self.navigationInterface.addItem(
"settingInterface", FluentIcon.SETTING, "设置", position=NavigationItemPosition.BOTTOM, selectable=False)
self.resize(880, 760)
self.setWindowTitle('PyQt-Fluent-Widgets')
self.setWindowIcon(QIcon(':/qfluentwidgets/images/logo.png'))
self.titleBar.raise_()
if __name__ == '__main__':
# setTheme(Theme.DARK)
app = QApplication(sys.argv)
w1 = Demo1()
w1.show()
w2 = Demo2()
w2.show()
w3 = Demo3()
w3.show()
app.exec()