462 lines
17 KiB
Python
462 lines
17 KiB
Python
# coding:utf-8
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
from PyQt5.QtCore import Qt, QPoint, QSize, QUrl, QRect
|
||
from PyQt5.QtGui import QIcon, QFont, QColor, QPainter
|
||
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QVBoxLayout, QSizePolicy
|
||
|
||
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, SingleDirectionScrollArea,
|
||
VerticalSeparator)
|
||
|
||
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://github.com/zhiyiYo/PyQt-Fluent-Widgets'), '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()
|
||
|
||
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.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('描述')
|
||
|
||
|
||
class SystemRequirementCard(HeaderCardWidget):
|
||
""" System requirements card """
|
||
|
||
def __init__(self, parent=None):
|
||
super().__init__(parent)
|
||
self.setTitle('系统要求')
|
||
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 LightBox(QWidget):
|
||
""" Light box """
|
||
|
||
def __init__(self, parent=None):
|
||
super().__init__(parent=parent)
|
||
if isDarkTheme():
|
||
tintColor = QColor(32, 32, 32, 200)
|
||
luminosityColor = QColor(0, 0, 0, 0)
|
||
else:
|
||
tintColor = QColor(255, 255, 255, 160)
|
||
luminosityColor = QColor(255, 255, 255, 0)
|
||
|
||
self.acrylicBrush = AcrylicBrush(self, 30, tintColor, luminosityColor)
|
||
|
||
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.hide)
|
||
|
||
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 showEvent(self, e):
|
||
rect = QRect(self.mapToGlobal(QPoint()), self.size())
|
||
self.acrylicBrush.grabImage(rect)
|
||
super().showEvent(e)
|
||
|
||
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))
|
||
|
||
|
||
|
||
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 Demo3(MicaWindow):
|
||
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.setWindowTitle('PyQt-Fluent-Widgets')
|
||
self.setWindowIcon(QIcon(':/qfluentwidgets/images/logo.png'))
|
||
|
||
self.hBoxLayout = QHBoxLayout(self)
|
||
self.scrollArea = SingleDirectionScrollArea(self)
|
||
self.view = QWidget(self)
|
||
|
||
self.vBoxLayout = QVBoxLayout(self.view)
|
||
self.appCard = AppInfoCard(self)
|
||
self.galleryCard = GalleryCard(self)
|
||
self.descriptionCard = DescriptionCard(self)
|
||
self.systemCard = SystemRequirementCard(self)
|
||
|
||
self.lightBox = LightBox(self)
|
||
self.lightBox.hide()
|
||
self.galleryCard.flipView.itemClicked.connect(self.showLightBox)
|
||
|
||
self.scrollArea.setWidget(self.view)
|
||
self.scrollArea.setWidgetResizable(True)
|
||
|
||
self.hBoxLayout.setContentsMargins(0, 48, 0, 0)
|
||
self.hBoxLayout.addWidget(self.scrollArea)
|
||
|
||
self.vBoxLayout.setSpacing(10)
|
||
self.vBoxLayout.setContentsMargins(30, 0, 30, 30)
|
||
self.vBoxLayout.addWidget(self.appCard, 0, Qt.AlignTop)
|
||
self.vBoxLayout.addWidget(self.galleryCard, 0, Qt.AlignTop)
|
||
self.vBoxLayout.addWidget(self.descriptionCard, 0, Qt.AlignTop)
|
||
self.vBoxLayout.addWidget(self.systemCard, 0, Qt.AlignTop)
|
||
|
||
self.resize(780, 800)
|
||
self.titleBar.raise_()
|
||
self.scrollArea.setStyleSheet(
|
||
"QScrollArea {border: none; background:transparent}")
|
||
self.view.setStyleSheet('QWidget {background:transparent}')
|
||
|
||
def showLightBox(self):
|
||
index = self.galleryCard.flipView.currentIndex()
|
||
self.lightBox.setCurrentIndex(index)
|
||
self.lightBox.show()
|
||
|
||
def resizeEvent(self, e):
|
||
super().resizeEvent(e)
|
||
self.lightBox.resize(self.width(), self.height() - 48)
|
||
self.lightBox.move(0, 48)
|
||
|
||
|
||
if __name__ == '__main__':
|
||
# enable dpi scale
|
||
QApplication.setHighDpiScaleFactorRoundingPolicy(
|
||
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
|
||
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
|
||
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
|
||
|
||
# setTheme(Theme.DARK)
|
||
|
||
app = QApplication(sys.argv)
|
||
w1 = Demo1()
|
||
w1.show()
|
||
w2 = Demo2()
|
||
w2.show()
|
||
w3 = Demo3()
|
||
w3.show()
|
||
app.exec_()
|