提交 315fdcb6 编写于 作者: 之一Yo's avatar 之一Yo

添加 InfoBar 组件

上级 88f7ca83
...@@ -257,5 +257,13 @@ class HomeInterface(ScrollArea): ...@@ -257,5 +257,13 @@ class HomeInterface(ScrollArea):
routeKey="statusInfoInterface", routeKey="statusInfoInterface",
index=1 index=1
) )
stateInfoView.addSampleCard(
icon="app/resource/images/controls/InfoBar.png",
title="InfoBar",
content=self.tr(
"An inline message to display app-wide status change information."),
routeKey="statusInfoInterface",
index=3
)
self.vBoxLayout.addWidget(stateInfoView) self.vBoxLayout.addWidget(stateInfoView)
...@@ -211,7 +211,7 @@ class MainWindow(FramelessWindow): ...@@ -211,7 +211,7 @@ class MainWindow(FramelessWindow):
def initWindow(self): def initWindow(self):
self.resize(960, 780) self.resize(960, 780)
self.setMinimumWidth(580) self.setMinimumWidth(760)
self.setWindowIcon(QIcon('app/resource/images/logo.png')) self.setWindowIcon(QIcon('app/resource/images/logo.png'))
self.setWindowTitle('PyQt-Fluent-Widgets') self.setWindowTitle('PyQt-Fluent-Widgets')
self.titleBar.setAttribute(Qt.WA_StyledBackground) self.titleBar.setAttribute(Qt.WA_StyledBackground)
......
# coding:utf-8 # coding:utf-8
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap from PyQt5.QtGui import QPixmap, QColor
from PyQt5.QtWidgets import QWidget, QLabel from PyQt5.QtWidgets import QWidget, QHBoxLayout
from qfluentwidgets import StateToolTip, ToolTipFilter, PushButton, PixmapLabel from qfluentwidgets import (StateToolTip, ToolTipFilter, PushButton, PixmapLabel,
InfoBar, InfoBarIcon, FluentIcon, InfoBarPosition)
from .gallery_interface import GalleryInterface from .gallery_interface import GalleryInterface
from ..common.translator import Translator from ..common.translator import Translator
...@@ -19,6 +20,7 @@ class StatusInfoInterface(GalleryInterface): ...@@ -19,6 +20,7 @@ class StatusInfoInterface(GalleryInterface):
parent=parent parent=parent
) )
# state tool tip
self.stateTooltip = None self.stateTooltip = None
button = PushButton(self.tr('Show StateToolTip')) button = PushButton(self.tr('Show StateToolTip'))
button.clicked.connect(self.onStateButtonClicked) button.clicked.connect(self.onStateButtonClicked)
...@@ -28,6 +30,7 @@ class StatusInfoInterface(GalleryInterface): ...@@ -28,6 +30,7 @@ class StatusInfoInterface(GalleryInterface):
'https://github.com/zhiyiYo/PyQt-Fluent-Widgets/blob/master/examples/status_tool_tip/demo.py' 'https://github.com/zhiyiYo/PyQt-Fluent-Widgets/blob/master/examples/status_tool_tip/demo.py'
) )
# tool tip
button = PushButton(self.tr('Button with a simple ToolTip')) button = PushButton(self.tr('Button with a simple ToolTip'))
button.installEventFilter(ToolTipFilter(button)) button.installEventFilter(ToolTipFilter(button))
button.setToolTip(self.tr('Simple ToolTip')) button.setToolTip(self.tr('Simple ToolTip'))
...@@ -50,9 +53,89 @@ class StatusInfoInterface(GalleryInterface): ...@@ -50,9 +53,89 @@ class StatusInfoInterface(GalleryInterface):
'https://github.com/zhiyiYo/PyQt-Fluent-Widgets/blob/master/examples/tool_tip/demo.py' 'https://github.com/zhiyiYo/PyQt-Fluent-Widgets/blob/master/examples/tool_tip/demo.py'
) )
# short info bar
infoBar = InfoBar(
icon=InfoBarIcon.SUCCESS,
title=self.tr('Success'),
content=self.tr("Essential app message for your users."),
orient=Qt.Horizontal,
isClosable=True,
duration=-1,
position=InfoBarPosition.NONE,
parent=self
)
self.addExampleCard(
self.tr('A closable InfoBar'),
infoBar,
'https://github.com/zhiyiYo/PyQt-Fluent-Widgets/blob/master/examples/info_bar/demo.py'
)
# long info bar
content = self.tr("A long essential app message for your users to be informed of, acknowledge, or take action on. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin dapibus dolor vitae justo rutrum, ut lobortis nibh mattis. Aenean id elit commodo, semper felis nec.")
infoBar = InfoBar(
icon=InfoBarIcon.WARNING,
title=self.tr('Warning'),
content=content,
orient=Qt.Vertical,
isClosable=True,
duration=-1,
position=InfoBarPosition.NONE,
parent=self
)
self.addExampleCard(
self.tr('A closable InfoBar with long message'),
infoBar,
'https://github.com/zhiyiYo/PyQt-Fluent-Widgets/blob/master/examples/info_bar/demo.py',
)
# add custom widget to info bar
infoBar = InfoBar(
icon=FluentIcon.GITHUB,
title=self.tr('GitHub'),
content=self.tr("When you look long into an abyss, the abyss looks into you."),
orient=Qt.Horizontal,
isClosable=True,
duration=-1,
position=InfoBarPosition.NONE,
parent=self
)
infoBar.addWidget(PushButton('Action'))
infoBar.setCustomBackgroundColor("white", "#202020")
self.addExampleCard(
self.tr('An InfoBar with custom icon, background color and widget.'),
infoBar,
'https://github.com/zhiyiYo/PyQt-Fluent-Widgets/blob/master/examples/info_bar/demo.py',
)
# different type info bar
w = QWidget(self)
hBoxLayout = QHBoxLayout(w)
button1 = PushButton(self.tr('Information'), w)
button2 = PushButton(self.tr('Success'), w)
button3 = PushButton(self.tr('Warning'), w)
button4 = PushButton(self.tr('Error'), w)
button1.clicked.connect(self.createInfoInfoBar)
button2.clicked.connect(self.createSuccessInfoBar)
button3.clicked.connect(self.createWarningInfoBar)
button4.clicked.connect(self.createErrorInfoBar)
hBoxLayout.addWidget(button1)
hBoxLayout.addWidget(button2)
hBoxLayout.addWidget(button3)
hBoxLayout.addWidget(button4)
hBoxLayout.setContentsMargins(0, 0, 0, 0)
hBoxLayout.setSpacing(15)
self.addExampleCard(
self.tr('InfoBar with different pop-up locations'),
w,
'https://github.com/zhiyiYo/PyQt-Fluent-Widgets/blob/master/examples/info_bar/demo.py',
)
def onStateButtonClicked(self): def onStateButtonClicked(self):
if self.stateTooltip: if self.stateTooltip:
self.stateTooltip.setContent(self.tr('The model training is complete!') +' 😆') self.stateTooltip.setContent(
self.tr('The model training is complete!') + ' 😆')
self.sender().setText(self.tr('Show StateToolTip')) self.sender().setText(self.tr('Show StateToolTip'))
self.stateTooltip.setState(True) self.stateTooltip.setState(True)
self.stateTooltip = None self.stateTooltip = None
...@@ -62,3 +145,52 @@ class StatusInfoInterface(GalleryInterface): ...@@ -62,3 +145,52 @@ class StatusInfoInterface(GalleryInterface):
self.sender().setText(self.tr('Hide StateToolTip')) self.sender().setText(self.tr('Hide StateToolTip'))
self.stateTooltip.move(self.stateTooltip.getSuitablePos()) self.stateTooltip.move(self.stateTooltip.getSuitablePos())
self.stateTooltip.show() self.stateTooltip.show()
def createInfoInfoBar(self):
content = "A long essential app message for your users to be informed of, acknowledge, or take action on. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin dapibus dolor vitae justo rutrum, ut lobortis nibh mattis. Aenean id elit commodo, semper felis nec."
w = InfoBar(
icon=InfoBarIcon.INFORMATION,
title='Information',
content=content,
orient=Qt.Vertical, # vertical layout
isClosable=True,
position=InfoBarPosition.TOP_RIGHT,
duration=2000,
parent=self
)
w.show()
def createSuccessInfoBar(self):
content = "A short essential success app message."
# convenient static mothod
InfoBar.success(
title='Title',
content=content,
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.TOP,
duration=2000,
parent=self
)
def createWarningInfoBar(self):
InfoBar.warning(
title='Title',
content="A short essential app warning message.",
orient=Qt.Horizontal,
isClosable=False, # disable close button
position=InfoBarPosition.TOP_LEFT,
duration=2000,
parent=self
)
def createErrorInfoBar(self):
InfoBar.error(
title=self.tr('No Internet'),
content="A error message which won't disappear automatically.",
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.BOTTOM_RIGHT,
duration=-1, # won't disappear automatically
parent=self
)
# coding:utf-8
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout
from qfluentwidgets import InfoBarIcon, InfoBar, PushButton, setTheme, Theme, FluentIcon, InfoBarPosition
class Demo(QWidget):
def __init__(self):
super().__init__()
# setTheme(Theme.DARK)
self.hBoxLayout = QHBoxLayout(self)
self.button1 = PushButton('Information', self)
self.button2 = PushButton('Success', self)
self.button3 = PushButton('Warning', self)
self.button4 = PushButton('Error', self)
self.button1.clicked.connect(self.createInfoInfoBar)
self.button2.clicked.connect(self.createSuccessInfoBar)
self.button3.clicked.connect(self.createWarningInfoBar)
self.button4.clicked.connect(self.createErrorInfoBar)
self.hBoxLayout.addWidget(self.button1)
self.hBoxLayout.addWidget(self.button2)
self.hBoxLayout.addWidget(self.button3)
self.hBoxLayout.addWidget(self.button4)
self.hBoxLayout.setContentsMargins(30, 0, 30, 0)
self.resize(700, 700)
def createInfoInfoBar(self):
content = "A long essential app message for your users to be informed of, acknowledge, or take action on. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin dapibus dolor vitae justo rutrum, ut lobortis nibh mattis. Aenean id elit commodo, semper felis nec."
w = InfoBar(
icon=InfoBarIcon.INFORMATION,
title='Title',
content=content,
orient=Qt.Vertical, # vertical layout
isClosable=True,
position=InfoBarPosition.TOP_RIGHT,
duration=2000,
parent=self
)
w.addWidget(PushButton('Action'))
w.show()
def createSuccessInfoBar(self):
content = "A short essential success app message."
# convenient static mothod
InfoBar.success(
title='Title',
content=content,
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.TOP,
duration=2000,
parent=self
)
def createWarningInfoBar(self):
InfoBar.warning(
title='Title',
content="A short essential app warning message.",
orient=Qt.Horizontal,
isClosable=False, # disable close button
position=InfoBarPosition.TOP_LEFT,
duration=2000,
parent=self
)
def createErrorInfoBar(self):
InfoBar.error(
title='Title',
content="A short essential app error message.",
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.BOTTOM_RIGHT,
duration=-1, # won't disappear automatically
parent=self
)
if __name__ == '__main__':
# enable dpi scale
QApplication.setHighDpiScaleFactorRoundingPolicy(
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
app = QApplication(sys.argv)
w = Demo()
w.show()
app.exec_()
...@@ -12,7 +12,7 @@ Examples are available at https://github.com/zhiyiYo/PyQt-Fluent-Widgets/tree/ma ...@@ -12,7 +12,7 @@ Examples are available at https://github.com/zhiyiYo/PyQt-Fluent-Widgets/tree/ma
:license: GPLv3, see LICENSE for more details. :license: GPLv3, see LICENSE for more details.
""" """
__version__ = "0.4.8" __version__ = "0.4.9"
from .components import * from .components import *
from .common import * from .common import *
......
<?xml version="1.0" encoding="utf-8"?>
<svg id="" width="16" height="16" style="width:16px;height:16px;" version="1.1"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" enable-background="new 0 0 2048 2048"
xml:space="preserve">
<path fill="#FF99A4" d="M0 1024 q0 -140.8 36.8 -271.46 q36.8 -130.67 103.46 -244.8 q66.67 -114.13 160 -207.47 q93.34 -93.33 207.47 -160 q114.13 -66.66 244.8 -103.46 q130.66 -36.8 271.46 -36.8 q140.8 0 271.47 36.8 q130.66 36.8 244.26 103.46 q113.6 66.67 207.47 160.54 q93.87 93.87 160.54 207.47 q66.66 113.6 103.46 244.27 q36.8 130.66 36.8 271.46 q0 140.8 -36.8 271.47 q-36.8 130.66 -103.46 244.8 q-66.67 114.13 -160 207.47 q-93.34 93.33 -207.47 160 q-114.13 66.66 -244.8 103.46 q-130.67 36.8 -271.47 36.8 q-141.87 0 -272.54 -36.8 q-130.66 -36.8 -244.26 -103.46 q-113.6 -66.67 -206.94 -160 q-93.33 -93.34 -160 -206.93 q-66.66 -113.6 -103.46 -244.8 q-36.8 -131.2 -36.8 -272 Z"/>
<path fill="#000" d="M648.53 1484.8 q16 0 32.54 -6.4 q16.53 -6.4 28.26 -18.13 l314.67 -315.74 l314.67 315.74 q11.73 11.73 28.26 18.13 q16.54 6.4 32.54 6.4 q36.26 0 60.79 -24.54 q24.54 -24.53 24.54 -60.79 q0 -36.27 -24.53 -60.8 l-315.74 -314.67 l315.74 -314.67 q24.53 -24.53 24.53 -60.8 q0 -36.26 -24.54 -60.79 q-24.53 -24.53 -60.79 -24.53 q-36.27 0 -60.8 24.53 l-314.67 315.74 l-314.67 -315.74 q-24.53 -24.53 -60.8 -24.53 q-36.26 0 -60.79 24.53 q-24.53 24.53 -24.53 60.79 q0 16 6.4 32.54 q6.4 16.53 18.13 28.26 l315.74 314.67 l-315.74 314.67 q-11.73 11.73 -18.13 28.26 q-6.4 16.54 -6.4 32.54 q0 35.2 25.07 60.27 q25.06 25.06 60.26 25.06 Z"/>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<svg id="" width="16" height="16" style="width:16px;height:16px;" version="1.1"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" enable-background="new 0 0 2048 2048"
xml:space="preserve">
<path fill="#C42B1C" d="M0 1024 q0 -140.8 36.8 -271.46 q36.8 -130.67 103.46 -244.8 q66.67 -114.13 160 -207.47 q93.34 -93.33 207.47 -160 q114.13 -66.66 244.8 -103.46 q130.66 -36.8 271.46 -36.8 q140.8 0 271.47 36.8 q130.66 36.8 244.26 103.46 q113.6 66.67 207.47 160.54 q93.87 93.87 160.54 207.47 q66.66 113.6 103.46 244.27 q36.8 130.66 36.8 271.46 q0 140.8 -36.8 271.47 q-36.8 130.66 -103.46 244.8 q-66.67 114.13 -160 207.47 q-93.34 93.33 -207.47 160 q-114.13 66.66 -244.8 103.46 q-130.67 36.8 -271.47 36.8 q-141.87 0 -272.54 -36.8 q-130.66 -36.8 -244.26 -103.46 q-113.6 -66.67 -206.94 -160 q-93.33 -93.34 -160 -206.93 q-66.66 -113.6 -103.46 -244.8 q-36.8 -131.2 -36.8 -272 Z"/>
<path fill="#ffffff" d="M648.53 1484.8 q16 0 32.54 -6.4 q16.53 -6.4 28.26 -18.13 l314.67 -315.74 l314.67 315.74 q11.73 11.73 28.26 18.13 q16.54 6.4 32.54 6.4 q36.26 0 60.79 -24.54 q24.54 -24.53 24.54 -60.79 q0 -36.27 -24.53 -60.8 l-315.74 -314.67 l315.74 -314.67 q24.53 -24.53 24.53 -60.8 q0 -36.26 -24.54 -60.79 q-24.53 -24.53 -60.79 -24.53 q-36.27 0 -60.8 24.53 l-314.67 315.74 l-314.67 -315.74 q-24.53 -24.53 -60.8 -24.53 q-36.26 0 -60.79 24.53 q-24.53 24.53 -24.53 60.79 q0 16 6.4 32.54 q6.4 16.53 18.13 28.26 l315.74 314.67 l-315.74 314.67 q-11.73 11.73 -18.13 28.26 q-6.4 16.54 -6.4 32.54 q0 35.2 25.07 60.27 q25.06 25.06 60.26 25.06 Z"/>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<svg id="" width="16" height="16" style="width:16px;height:16px;" version="1.1"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" enable-background="new 0 0 2048 2048"
xml:space="preserve">
<path fill="#29f7ff" d="M0 1024 q0 -140.8 36.8 -271.46 q36.8 -130.67 103.46 -244.8 q66.67 -114.13 160 -207.47 q93.34 -93.33 207.47 -160 q114.13 -66.66 244.8 -103.46 q130.66 -36.8 271.46 -36.8 q140.8 0 271.47 36.8 q130.66 36.8 244.26 103.46 q113.6 66.67 207.47 160.54 q93.87 93.87 160.54 207.47 q66.66 113.6 103.46 244.27 q36.8 130.66 36.8 271.46 q0 140.8 -36.8 271.47 q-36.8 130.66 -103.46 244.8 q-66.67 114.13 -160 207.47 q-93.34 93.33 -207.47 160 q-114.13 66.66 -244.8 103.46 q-130.67 36.8 -271.47 36.8 q-141.87 0 -272.54 -36.8 q-130.66 -36.8 -244.26 -103.46 q-113.6 -66.67 -206.94 -160 q-93.33 -93.34 -160 -206.93 q-66.66 -113.6 -103.46 -244.8 q-36.8 -131.2 -36.8 -272 Z"/>
<path fill="#000" d="M1126.4 614.4 q0 -42.67 -29.87 -72.53 q-29.87 -29.87 -72.54 -29.87 q-42.67 0 -72.53 29.87 q-29.87 29.87 -29.87 72.53 q0 42.67 29.87 72.53 q29.87 29.87 72.53 29.87 q42.67 0 72.54 -29.87 q29.87 -29.86 29.87 -72.53 ZM1092.27 1433.6 l0 -546.13 q0 -27.74 -20.27 -48 q-20.27 -20.26 -48 -20.26 q-27.73 0 -48 20.26 q-20.27 20.26 -20.27 48 l0 546.13 q0 27.73 20.27 48 q20.27 20.27 48 20.27 q27.73 0 48 -20.27 q20.27 -20.27 20.27 -48 Z"/>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<svg id="" width="16" height="16" style="width:16px;height:16px;" version="1.1"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" enable-background="new 0 0 2048 2048"
xml:space="preserve">
<path fill="#00b7c3" d="M0 1024 q0 -140.8 36.8 -271.46 q36.8 -130.67 103.46 -244.8 q66.67 -114.13 160 -207.47 q93.34 -93.33 207.47 -160 q114.13 -66.66 244.8 -103.46 q130.66 -36.8 271.46 -36.8 q140.8 0 271.47 36.8 q130.66 36.8 244.26 103.46 q113.6 66.67 207.47 160.54 q93.87 93.87 160.54 207.47 q66.66 113.6 103.46 244.27 q36.8 130.66 36.8 271.46 q0 140.8 -36.8 271.47 q-36.8 130.66 -103.46 244.8 q-66.67 114.13 -160 207.47 q-93.34 93.33 -207.47 160 q-114.13 66.66 -244.8 103.46 q-130.67 36.8 -271.47 36.8 q-141.87 0 -272.54 -36.8 q-130.66 -36.8 -244.26 -103.46 q-113.6 -66.67 -206.94 -160 q-93.33 -93.34 -160 -206.93 q-66.66 -113.6 -103.46 -244.8 q-36.8 -131.2 -36.8 -272 Z"/>
<path fill="#ffffff" d="M1126.4 614.4 q0 -42.67 -29.87 -72.53 q-29.87 -29.87 -72.54 -29.87 q-42.67 0 -72.53 29.87 q-29.87 29.87 -29.87 72.53 q0 42.67 29.87 72.53 q29.87 29.87 72.53 29.87 q42.67 0 72.54 -29.87 q29.87 -29.86 29.87 -72.53 ZM1092.27 1433.6 l0 -546.13 q0 -27.74 -20.27 -48 q-20.27 -20.26 -48 -20.26 q-27.73 0 -48 20.26 q-20.27 20.26 -20.27 48 l0 546.13 q0 27.73 20.27 48 q20.27 20.27 48 20.27 q27.73 0 48 -20.27 q20.27 -20.27 20.27 -48 Z"/>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<svg id="" width="16" height="16" style="width:16px;height:16px;" version="1.1"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" enable-background="new 0 0 2048 2048"
xml:space="preserve">
<path fill="#6CCB5F" d="M0 1024 q0 -140.8 36.8 -271.46 q36.8 -130.67 103.46 -244.8 q66.67 -114.13 160 -207.47 q93.34 -93.33 207.47 -160 q114.13 -66.66 244.8 -103.46 q130.66 -36.8 271.46 -36.8 q140.8 0 271.47 36.8 q130.66 36.8 244.26 103.46 q113.6 66.67 207.47 160.54 q93.87 93.87 160.54 207.47 q66.66 113.6 103.46 244.27 q36.8 130.66 36.8 271.46 q0 140.8 -36.8 271.47 q-36.8 130.66 -103.46 244.8 q-66.67 114.13 -160 207.47 q-93.34 93.33 -207.47 160 q-114.13 66.66 -244.8 103.46 q-130.67 36.8 -271.47 36.8 q-141.87 0 -272.54 -36.8 q-130.66 -36.8 -244.26 -103.46 q-113.6 -66.67 -206.94 -160 q-93.33 -93.34 -160 -206.93 q-66.66 -113.6 -103.46 -244.8 q-36.8 -131.2 -36.8 -272 Z"/>
<path fill="#000" d="M1518.93 750.93 q0 -36.26 -24.53 -60.79 q-24.53 -24.53 -60.8 -24.53 q-36.27 0 -60.8 24.53 l-485.33 486.4 l-212.27 -213.33 q-24.53 -24.53 -60.8 -24.53 q-36.27 0 -60.8 24.53 q-24.53 24.53 -24.53 60.8 q0 16 6.4 32.53 q6.4 16.54 18.13 28.27 l273.07 273.07 q11.73 11.73 28.26 18.13 q16.54 6.4 32.54 6.4 q16 0 32.53 -6.4 q16.53 -6.4 28.27 -18.13 l546.13 -546.14 q24.53 -24.53 24.53 -60.8 Z"/>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<svg id="" width="16" height="16" style="width:16px;height:16px;" version="1.1"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" enable-background="new 0 0 2048 2048"
xml:space="preserve">
<path fill="#0F7B0F" d="M0 1024 q0 -140.8 36.8 -271.46 q36.8 -130.67 103.46 -244.8 q66.67 -114.13 160 -207.47 q93.34 -93.33 207.47 -160 q114.13 -66.66 244.8 -103.46 q130.66 -36.8 271.46 -36.8 q140.8 0 271.47 36.8 q130.66 36.8 244.26 103.46 q113.6 66.67 207.47 160.54 q93.87 93.87 160.54 207.47 q66.66 113.6 103.46 244.27 q36.8 130.66 36.8 271.46 q0 140.8 -36.8 271.47 q-36.8 130.66 -103.46 244.8 q-66.67 114.13 -160 207.47 q-93.34 93.33 -207.47 160 q-114.13 66.66 -244.8 103.46 q-130.67 36.8 -271.47 36.8 q-141.87 0 -272.54 -36.8 q-130.66 -36.8 -244.26 -103.46 q-113.6 -66.67 -206.94 -160 q-93.33 -93.34 -160 -206.93 q-66.66 -113.6 -103.46 -244.8 q-36.8 -131.2 -36.8 -272 Z"/>
<path fill="#ffffff" d="M1518.93 750.93 q0 -36.26 -24.53 -60.79 q-24.53 -24.53 -60.8 -24.53 q-36.27 0 -60.8 24.53 l-485.33 486.4 l-212.27 -213.33 q-24.53 -24.53 -60.8 -24.53 q-36.27 0 -60.8 24.53 q-24.53 24.53 -24.53 60.8 q0 16 6.4 32.53 q6.4 16.54 18.13 28.27 l273.07 273.07 q11.73 11.73 28.26 18.13 q16.54 6.4 32.54 6.4 q16 0 32.53 -6.4 q16.53 -6.4 28.27 -18.13 l546.13 -546.14 q24.53 -24.53 24.53 -60.8 Z"/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<svg id="" width="16" height="16" style="width:16px;height:16px;" version="1.1"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" enable-background="new 0 0 2048 2048"
xml:space="preserve">
<path fill="#FCE100" d="M1024 2048 q-140.8 0 -272 -36.8 q-131.2 -36.8 -244.8 -103.46 q-113.6 -66.67 -206.94 -160 q-93.33 -93.34 -160 -206.93 q-66.66 -113.6 -103.46 -244.27 q-36.8 -130.66 -36.8 -272.53 q0 -140.8 36.8 -271.46 q36.8 -130.67 103.46 -244.8 q66.67 -114.13 160 -207.47 q93.34 -93.33 207.47 -160 q114.13 -66.66 244.8 -103.46 q130.66 -36.8 271.46 -36.8 q140.8 0 271.47 36.8 q130.66 36.8 244.26 103.46 q113.6 66.67 207.47 160.54 q93.87 93.87 160.54 207.47 q66.66 113.6 103.46 244.27 q36.8 130.66 36.8 271.46 q0 140.8 -36.8 271.47 q-36.8 130.66 -103.46 244.8 q-66.67 114.13 -160 207.47 q-93.34 93.33 -207.47 160 q-114.13 66.66 -244.8 103.46 q-130.67 36.8 -271.47 36.8 Z"/>
<path fill="#000" d="M1126.4 1399.47 q0 -42.67 -29.87 -72.54 q-29.87 -29.87 -72.54 -29.87 q-42.67 0 -72.53 29.87 q-29.87 29.87 -29.87 72.54 q0 42.66 29.87 72.53 q29.87 29.87 72.53 29.87 q42.67 0 72.54 -29.87 q29.87 -29.87 29.87 -72.53 ZM1092.27 580.27 q0 -27.74 -20.27 -48 q-20.27 -20.26 -48 -20.26 q-27.73 0 -48 20.26 q-20.27 20.26 -20.27 48 l0 546.13 q0 27.73 20.27 48 q20.27 20.27 48 20.27 q27.73 0 48 -20.27 q20.27 -20.27 20.27 -48 l0 -546.13 Z"/>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<svg id="" width="16" height="16" style="width:16px;height:16px;" version="1.1"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" enable-background="new 0 0 2048 2048"
xml:space="preserve">
<path fill="#9D5D00" d="M1024 2048 q-140.8 0 -272 -36.8 q-131.2 -36.8 -244.8 -103.46 q-113.6 -66.67 -206.94 -160 q-93.33 -93.34 -160 -206.93 q-66.66 -113.6 -103.46 -244.27 q-36.8 -130.66 -36.8 -272.53 q0 -140.8 36.8 -271.46 q36.8 -130.67 103.46 -244.8 q66.67 -114.13 160 -207.47 q93.34 -93.33 207.47 -160 q114.13 -66.66 244.8 -103.46 q130.66 -36.8 271.46 -36.8 q140.8 0 271.47 36.8 q130.66 36.8 244.26 103.46 q113.6 66.67 207.47 160.54 q93.87 93.87 160.54 207.47 q66.66 113.6 103.46 244.27 q36.8 130.66 36.8 271.46 q0 140.8 -36.8 271.47 q-36.8 130.66 -103.46 244.8 q-66.67 114.13 -160 207.47 q-93.34 93.33 -207.47 160 q-114.13 66.66 -244.8 103.46 q-130.67 36.8 -271.47 36.8 Z"/>
<path fill="#ffffff" d="M1126.4 1399.47 q0 -42.67 -29.87 -72.54 q-29.87 -29.87 -72.54 -29.87 q-42.67 0 -72.53 29.87 q-29.87 29.87 -29.87 72.54 q0 42.66 29.87 72.53 q29.87 29.87 72.53 29.87 q42.67 0 72.54 -29.87 q29.87 -29.87 29.87 -72.53 ZM1092.27 580.27 q0 -27.74 -20.27 -48 q-20.27 -20.26 -48 -20.26 q-27.73 0 -48 20.26 q-20.27 20.26 -20.27 48 l0 546.13 q0 27.73 20.27 48 q20.27 20.27 48 20.27 q27.73 0 48 -20.27 q20.27 -20.27 20.27 -48 l0 -546.13 Z"/>
</svg>
\ No newline at end of file
InfoBar {
border: 1px solid rgb(29, 29, 29);
border-radius: 6px;
background-color: rgb(39, 39, 39);
}
#titleLabel {
font: 14px 'Segoe UI', 'Microsoft YaHei';
font-weight: bold;
color: white;
}
#contentLabel {
font: 14px 'Segoe UI', 'Microsoft YaHei';
color: white;
}
InfoBar[type="Info"] {
background-color: rgb(39, 39, 39);
}
InfoBar[type="Success"] {
background-color: rgb(57, 61, 27);
}
InfoBar[type="Warning"] {
background-color: rgb(67, 53, 25);
}
InfoBar[type="Error"] {
background-color: rgb(68, 39, 38);
}
InfoBarCloseButton {
background-color: transparent;
border-radius: 5px;
}
InfoBarCloseButton:hover {
background-color: rgba(255, 255, 255, 9);
}
InfoBarCloseButton:pressed {
background-color: rgba(255, 255, 255, 6);
}
\ No newline at end of file
InfoBar {
border: 1px solid rgb(229, 229, 229);
border-radius: 6px;
background-color: rgb(244, 244, 244);
}
#titleLabel {
font: 14px 'Segoe UI', 'Microsoft YaHei';
font-weight: bold;
color: black;
}
#contentLabel {
font: 14px 'Segoe UI', 'Microsoft YaHei';
color: black;
}
InfoBar[type="Info"] {
background-color: rgb(244, 244, 244);
}
InfoBar[type="Success"] {
background-color: rgb(223, 246, 221);
}
InfoBar[type="Warning"] {
background-color: rgb(255, 244, 206);
}
InfoBar[type="Error"] {
background-color: rgb(253, 231, 233);
}
InfoBarCloseButton {
background-color: transparent;
border-radius: 5px;
}
InfoBarCloseButton:hover {
background-color: rgba(0, 0, 0, 9);
}
InfoBarCloseButton:pressed {
background-color: rgba(0, 0, 0, 6);
}
\ No newline at end of file
此差异已折叠。
...@@ -118,6 +118,14 @@ ...@@ -118,6 +118,14 @@
<file>images/check_box/Accept_white.svg</file> <file>images/check_box/Accept_white.svg</file>
<file>images/check_box/PartialAccept_black.svg</file> <file>images/check_box/PartialAccept_black.svg</file>
<file>images/check_box/PartialAccept_white.svg</file> <file>images/check_box/PartialAccept_white.svg</file>
<file>images/info_bar/Error_light.svg</file>
<file>images/info_bar/Error_dark.svg</file>
<file>images/info_bar/Success_light.svg</file>
<file>images/info_bar/Success_dark.svg</file>
<file>images/info_bar/Warning_light.svg</file>
<file>images/info_bar/Warning_dark.svg</file>
<file>images/info_bar/Info_light.svg</file>
<file>images/info_bar/Info_dark.svg</file>
<file>qss/dark/color_dialog.qss</file> <file>qss/dark/color_dialog.qss</file>
<file>qss/dark/dialog.qss</file> <file>qss/dark/dialog.qss</file>
...@@ -136,6 +144,7 @@ ...@@ -136,6 +144,7 @@
<file>qss/dark/slider.qss</file> <file>qss/dark/slider.qss</file>
<file>qss/dark/line_edit.qss</file> <file>qss/dark/line_edit.qss</file>
<file>qss/dark/check_box.qss</file> <file>qss/dark/check_box.qss</file>
<file>qss/dark/info_bar.qss</file>
<file>qss/light/color_dialog.qss</file> <file>qss/light/color_dialog.qss</file>
<file>qss/light/dialog.qss</file> <file>qss/light/dialog.qss</file>
...@@ -154,5 +163,6 @@ ...@@ -154,5 +163,6 @@
<file>qss/light/slider.qss</file> <file>qss/light/slider.qss</file>
<file>qss/light/line_edit.qss</file> <file>qss/light/line_edit.qss</file>
<file>qss/light/check_box.qss</file> <file>qss/light/check_box.qss</file>
<file>qss/light/info_bar.qss</file>
</qresource> </qresource>
</RCC> </RCC>
\ No newline at end of file
# coding:utf-8 # coding:utf-8
from enum import Enum from enum import Enum
from PyQt5.QtCore import QPoint, QRect, QRectF, Qt from PyQt5.QtXml import QDomDocument
from PyQt5.QtGui import QIcon, QIconEngine, QImage, QPainter, QPixmap from PyQt5.QtCore import QPoint, QRect, QRectF, Qt, QFile
from PyQt5.QtGui import QIcon, QIconEngine, QImage, QPainter, QPixmap, QColor
from PyQt5.QtSvg import QSvgRenderer from PyQt5.QtSvg import QSvgRenderer
from .config import isDarkTheme, Theme from .config import isDarkTheme, Theme
...@@ -52,13 +53,13 @@ def getIconColor(): ...@@ -52,13 +53,13 @@ def getIconColor():
return "white" if isDarkTheme() else 'black' return "white" if isDarkTheme() else 'black'
def drawSvgIcon(iconPath, painter, rect): def drawSvgIcon(icon, painter, rect):
""" draw svg icon """ draw svg icon
Parameters Parameters
---------- ----------
iconPath: str icon: str | bytes | QByteArray
the path of svg icon the path or code of svg icon
painter: QPainter painter: QPainter
painter painter
...@@ -66,10 +67,52 @@ def drawSvgIcon(iconPath, painter, rect): ...@@ -66,10 +67,52 @@ def drawSvgIcon(iconPath, painter, rect):
rect: QRect | QRectF rect: QRect | QRectF
the rect to render icon the rect to render icon
""" """
renderer = QSvgRenderer(iconPath) renderer = QSvgRenderer(icon)
renderer.render(painter, QRectF(rect)) renderer.render(painter, QRectF(rect))
def writeSvg(iconPath: str, indexes=None, **attributes):
""" write svg with specified attributes
Parameters
----------
iconPath: str
svg icon path
indexes: List[int]
the path to be filled
**attributes:
the attributes of path
Returns
-------
svg: str
svg code
"""
if not iconPath.lower().endswith('.svg'):
return ""
f = QFile(iconPath)
f.open(QFile.ReadOnly)
dom = QDomDocument()
dom.setContent(f.readAll())
f.close()
# change the color of each path
pathNodes = dom.elementsByTagName('path')
indexes = range(pathNodes.length()) if not indexes else indexes
for i in indexes:
element = pathNodes.at(i).toElement()
for k, v in attributes.items():
element.setAttribute(k, v)
return dom.toString()
def drawIcon(icon, painter, rect): def drawIcon(icon, painter, rect):
""" draw icon """ draw icon
...@@ -142,7 +185,6 @@ class FluentIconBase: ...@@ -142,7 +185,6 @@ class FluentIconBase:
drawSvgIcon(self.path(theme), painter, rect) drawSvgIcon(self.path(theme), painter, rect)
class FluentIcon(FluentIconBase, Enum): class FluentIcon(FluentIconBase, Enum):
""" Fluent icon """ """ Fluent icon """
...@@ -204,4 +246,3 @@ class FluentIcon(FluentIconBase, Enum): ...@@ -204,4 +246,3 @@ class FluentIcon(FluentIconBase, Enum):
c = "white" if theme == Theme.DARK else "black" c = "white" if theme == Theme.DARK else "black"
return f':/qfluentwidgets/images/icons/{self.value}_{c}.svg' return f':/qfluentwidgets/images/icons/{self.value}_{c}.svg'
...@@ -3,7 +3,6 @@ from enum import Enum ...@@ -3,7 +3,6 @@ from enum import Enum
from string import Template from string import Template
import weakref import weakref
import darkdetect
from PyQt5.QtCore import QFile, QObject from PyQt5.QtCore import QFile, QObject
from PyQt5.QtGui import QColor from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import QWidget from PyQt5.QtWidgets import QWidget
......
...@@ -11,3 +11,4 @@ from .line_edit import LineEdit ...@@ -11,3 +11,4 @@ from .line_edit import LineEdit
from .check_box import CheckBox from .check_box import CheckBox
from .icon_widget import IconWidget from .icon_widget import IconWidget
from .label import PixmapLabel from .label import PixmapLabel
from .info_bar import InfoBar, InfoBarIcon, InfoBarPosition
\ No newline at end of file
# coding:utf-8
from enum import Enum
from typing import Union
import weakref
from PyQt5.QtCore import (Qt, QEvent, QSize, QRectF, QObject, QPropertyAnimation,
QEasingCurve, QTimer, pyqtSignal, QParallelAnimationGroup, QPoint)
from PyQt5.QtGui import QPainter, QIcon, QColor
from PyQt5.QtWidgets import (QWidget, QFrame, QLabel, QHBoxLayout, QVBoxLayout,
QToolButton, QGraphicsOpacityEffect)
from ...common.auto_wrap import TextWrap
from ...common.style_sheet import setStyleSheet, themeColor
from ...common.icon import FluentIconBase, Theme, isDarkTheme, writeSvg, drawSvgIcon, drawIcon
from ...common.icon import FluentIcon as FIF
class InfoBarCloseButton(QToolButton):
""" Close button """
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setFixedSize(36, 36)
self.setIconSize(QSize(12, 12))
self.setCursor(Qt.PointingHandCursor)
self.setObjectName('infoBarCloseButton')
setStyleSheet(self, 'info_bar')
def paintEvent(self, e):
super().paintEvent(e)
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing |
QPainter.SmoothPixmapTransform)
FIF.CLOSE.render(painter, QRectF(11, 12, 12, 12))
class InfoBarIcon(FluentIconBase, Enum):
""" Info bar icon """
INFORMATION = "Info"
SUCCESS = "Success"
WARNING = "Warning"
ERROR = "Error"
def path(self, theme=Theme.AUTO):
if theme == Theme.AUTO:
color = "dark" if isDarkTheme() else "light"
else:
color = theme.value.lower()
return f':/qfluentwidgets/images/info_bar/{self.value}_{color}.svg'
class InfoBarPosition(Enum):
""" Info bar position """
TOP = 0
BOTTOM = 1
TOP_LEFT = 2
TOP_RIGHT = 3
BOTTOM_LEFT = 4
BOTTOM_RIGHT = 5
NONE = 6
class InfoIconWidget(QWidget):
""" Icon widget """
def __init__(self, icon: InfoBarIcon, parent=None):
super().__init__(parent=parent)
self.setFixedSize(15, 15)
self.icon = icon
def paintEvent(self, e):
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing |
QPainter.SmoothPixmapTransform)
if self.icon != InfoBarIcon.INFORMATION:
drawIcon(self.icon, painter, self.rect())
else:
svg = writeSvg(self.icon.path(), [0], fill=themeColor().name())
drawSvgIcon(svg.encode(), painter, self.rect())
class InfoBar(QFrame):
""" Information bar """
closedSignal = pyqtSignal()
def __init__(self, icon: Union[InfoBarIcon, FluentIconBase, QIcon, str], title: str, content: str,
orient=Qt.Horizontal, isClosable=True, duration=1000, position=InfoBarPosition.TOP_RIGHT,
parent=None):
"""
Parameters
----------
icon: InfoBarIcon | FluentIconBase | QIcon | str
the icon of info bar
title: str
the title of info bar
content: str
the content of info bar
orient: Qt.Orientation
the layout direction of info bar, use `Qt.Horizontal` for short content
isClosable: bool
whether to show the close button
duraction: int
the time for info bar to display in milliseconds. If duration is less than zero,
info bar will never disappear.
parent: QWidget
parent widget
"""
super().__init__(parent=parent)
self.title = title
self.content = content
self.orient = orient
self.icon = icon
self.duration = duration
self.isClosable = isClosable
self.position = position
self.titleLabel = QLabel(title, self)
self.contentLabel = QLabel(self)
self.closeButton = InfoBarCloseButton(self)
self.iconWidget = InfoIconWidget(icon)
self.vBoxLayout = QVBoxLayout(self)
self.hBoxLayout = QHBoxLayout()
self.contentLayout = QHBoxLayout() if self.orient == Qt.Horizontal else QVBoxLayout()
self.opacityEffect = QGraphicsOpacityEffect(self)
self.opacityAni = QPropertyAnimation(
self.opacityEffect, b'opacity', self)
self.lightBackgroundColor = None
self.darkBackgroundColor = None
self.__initWidget()
def __initWidget(self):
self.titleLabel.setMinimumHeight(36)
self.opacityEffect.setOpacity(1)
self.setGraphicsEffect(self.opacityEffect)
self.closeButton.setVisible(self.isClosable)
self.__initLayout()
self.__setQss()
self.closeButton.clicked.connect(self.close)
def __initLayout(self):
self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
self.vBoxLayout.setSizeConstraint(QVBoxLayout.SetMinimumSize)
self.contentLayout.setSizeConstraint(QHBoxLayout.SetMinimumSize)
self.vBoxLayout.setSpacing(0)
self.hBoxLayout.setSpacing(0)
self.vBoxLayout.addLayout(self.hBoxLayout)
self.hBoxLayout.addWidget(self.iconWidget)
self.hBoxLayout.addSpacing(15)
self.hBoxLayout.addWidget(self.titleLabel)
# add content label to layout
if self.orient == Qt.Horizontal:
self.vBoxLayout.setAlignment(Qt.AlignVCenter)
self.hBoxLayout.addSpacing(12)
self.contentLayout.setContentsMargins(0, 0, 20*self.isClosable, 0)
self.contentLayout.setSpacing(12)
self.hBoxLayout.addLayout(self.contentLayout)
self.contentLabel.setText(self.content)
else:
self.vBoxLayout.setAlignment(Qt.AlignTop)
self.contentLayout.setContentsMargins(47, 0, 40, 18)
self.contentLayout.setSpacing(14)
self.vBoxLayout.addLayout(self.contentLayout)
self.contentLayout.setAlignment(Qt.AlignTop)
self._adjustText()
self.contentLayout.addWidget(self.contentLabel)
# add close button to layout
if self.isClosable:
self.hBoxLayout.addWidget(self.closeButton)
mb = 6 if self.orient == Qt.Horizontal else 0
self.hBoxLayout.setContentsMargins(16, 6, 7, mb)
else:
self.hBoxLayout.setContentsMargins(16, 6, 16, 6)
def __setQss(self):
self.titleLabel.setObjectName('titleLabel')
self.contentLabel.setObjectName('contentLabel')
if isinstance(self.icon, Enum):
self.setProperty('type', self.icon.value)
setStyleSheet(self, 'info_bar')
def __fadeOut(self):
""" fade out """
self.opacityAni.setDuration(200)
self.opacityAni.setStartValue(1)
self.opacityAni.setEndValue(0)
self.opacityAni.finished.connect(self.close)
self.opacityAni.start()
def _adjustText(self):
w = 900 if not self.parent() else (self.parent().width() - 50)
chars = max(min(w / 9, 120), 30)
self.contentLabel.setText(TextWrap.wrap(self.content, chars, False)[0])
self.adjustSize()
def addWidget(self, widget: QWidget, stretch=0):
""" add widget to info bar """
self.contentLayout.addWidget(widget, stretch, Qt.AlignLeft)
def setCustomBackgroundColor(self, light, dark):
""" set the custom background color
Parameters
----------
light, dark: str | Qt.GlobalColor | QColor
background color in light/dark theme mode
"""
self.lightBackgroundColor = QColor(light)
self.darkBackgroundColor = QColor(dark)
self.update()
def eventFilter(self, obj, e: QEvent):
if obj is self.parent():
if e.type() in [QEvent.Resize, QEvent.WindowStateChange]:
self._adjustText()
return super().eventFilter(obj, e)
def closeEvent(self, e):
self.closedSignal.emit()
self.deleteLater()
def showEvent(self, e):
self._adjustText()
super().showEvent(e)
if self.duration >= 0:
QTimer.singleShot(self.duration, self.__fadeOut)
if self.position != InfoBarPosition.NONE:
manager = InfoBarManager.make(self.position)
manager.add(self)
if self.parent():
self.parent().installEventFilter(self)
def paintEvent(self, e):
super().paintEvent(e)
if self.lightBackgroundColor is None:
return
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing)
painter.setPen(Qt.NoPen)
if isDarkTheme():
painter.setBrush(self.darkBackgroundColor)
else:
painter.setBrush(self.lightBackgroundColor)
rect = self.rect().adjusted(1, 1, -1, -1)
painter.drawRoundedRect(rect, 6, 6)
@classmethod
def new(cls, icon, title, content, orient=Qt.Horizontal, isClosable=True, duration=1000,
position=InfoBarPosition.TOP_RIGHT, parent=None):
w = InfoBar(icon, title, content, orient,
isClosable, duration, position, parent)
w.show()
return w
@classmethod
def info(cls, title, content, orient=Qt.Horizontal, isClosable=True, duration=1000,
position=InfoBarPosition.TOP_RIGHT, parent=None):
return cls.new(InfoBarIcon.INFORMATION, title, content, orient, isClosable, duration, position, parent)
@classmethod
def success(cls, title, content, orient=Qt.Horizontal, isClosable=True, duration=1000,
position=InfoBarPosition.TOP_RIGHT, parent=None):
return cls.new(InfoBarIcon.SUCCESS, title, content, orient, isClosable, duration, position, parent)
@classmethod
def warning(cls, title, content, orient=Qt.Horizontal, isClosable=True, duration=1000,
position=InfoBarPosition.TOP_RIGHT, parent=None):
return cls.new(InfoBarIcon.WARNING, title, content, orient, isClosable, duration, position, parent)
@classmethod
def error(cls, title, content, orient=Qt.Horizontal, isClosable=True, duration=1000,
position=InfoBarPosition.TOP_RIGHT, parent=None):
return cls.new(InfoBarIcon.ERROR, title, content, orient, isClosable, duration, position, parent)
class InfoBarManager(QObject):
""" Info bar manager """
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super(InfoBarManager, cls).__new__(
cls, *args, **kwargs)
cls._instance.__initialized = False
return cls._instance
def __init__(self):
super().__init__()
if self.__initialized:
return
self.spacing = 16
self.margin = 24
self.infoBars = weakref.WeakKeyDictionary()
self.aniGroups = weakref.WeakKeyDictionary()
self.slideAnis = []
self.dropAnis = []
self.__initialized = True
def add(self, infoBar: InfoBar):
""" add info bar """
p = infoBar.parent() # type:QWidget
if not p:
return
if p not in self.infoBars:
p.installEventFilter(self)
self.infoBars[p] = []
self.aniGroups[p] = QParallelAnimationGroup(self)
if infoBar in self.infoBars[p]:
return
# add drop animation
if self.infoBars[p]:
dropAni = QPropertyAnimation(infoBar, b'pos')
dropAni.setDuration(200)
self.aniGroups[p].addAnimation(dropAni)
self.dropAnis.append(dropAni)
infoBar.setProperty('dropAni', dropAni)
# add slide animation
self.infoBars[p].append(infoBar)
slideAni = self._createSlideAni(infoBar)
self.slideAnis.append(slideAni)
infoBar.setProperty('slideAni', slideAni)
infoBar.closedSignal.connect(lambda: self.remove(infoBar))
slideAni.start()
def remove(self, infoBar: InfoBar):
""" remove info bar """
p = infoBar.parent()
if p not in self.infoBars:
return
if infoBar not in self.infoBars[p]:
return
self.infoBars[p].remove(infoBar)
# remove drop animation
dropAni = infoBar.property('dropAni') # type: QPropertyAnimation
if dropAni:
self.aniGroups[p].removeAnimation(dropAni)
self.dropAnis.remove(dropAni)
# remove slider animation
slideAni = infoBar.property('slideAni')
if slideAni:
self.slideAnis.remove(slideAni)
# adjust the position of the remaining info bars
self._updateDropAni(p)
self.aniGroups[p].start()
def _createSlideAni(self, infoBar: InfoBar):
slideAni = QPropertyAnimation(infoBar, b'pos')
slideAni.setEasingCurve(QEasingCurve.OutQuad)
slideAni.setDuration(200)
slideAni.setStartValue(self._slideStartPos(infoBar))
slideAni.setEndValue(self._pos(infoBar))
return slideAni
def _updateDropAni(self, parent):
for bar in self.infoBars[parent]:
ani = bar.property('dropAni')
if not ani:
continue
ani.setStartValue(bar.pos())
ani.setEndValue(self._pos(bar))
def _pos(self, infoBar: InfoBar, parentSize=None) -> QPoint:
raise NotImplementedError
def _slideStartPos(self, infoBar: InfoBar) -> QPoint:
raise NotImplementedError
def eventFilter(self, obj, e: QEvent):
if obj not in self.infoBars:
return False
if e.type() in [QEvent.Resize, QEvent.WindowStateChange]:
size = e.size() if e.type() == QEvent.Resize else None
for bar in self.infoBars[obj]:
bar.move(self._pos(bar, size))
return super().eventFilter(obj, e)
@staticmethod
def make(position: InfoBarPosition):
""" mask info bar manager according to the display position """
managers = {
InfoBarPosition.TOP: TopInfoBarManager,
InfoBarPosition.BOTTOM: BottomInfoBarManager,
InfoBarPosition.TOP_RIGHT: TopRightInfoBarManager,
InfoBarPosition.BOTTOM_RIGHT: BottomRightInfoBarManager,
InfoBarPosition.TOP_LEFT: TopLeftInfoBarManager,
InfoBarPosition.BOTTOM_LEFT: BottomLeftInfoBarManager,
}
if position not in managers:
raise ValueError(f'`{position}` is an invalid info bar position.')
return managers[position]()
class TopInfoBarManager(InfoBarManager):
""" Top position info bar manager """
def _pos(self, infoBar: InfoBar, parentSize=None):
p = infoBar.parent()
parentSize = parentSize or p.size()
x = (infoBar.parent().width() - infoBar.width()) // 2
y = self.margin
index = self.infoBars[p].index(infoBar)
for bar in self.infoBars[p][0:index]:
y += (bar.height() + self.spacing)
return QPoint(x, y)
def _slideStartPos(self, infoBar: InfoBar):
pos = self._pos(infoBar)
return QPoint(pos.x(), pos.y() - 16)
class TopRightInfoBarManager(InfoBarManager):
""" Top right position info bar manager """
def _pos(self, infoBar: InfoBar, parentSize=None):
p = infoBar.parent()
parentSize = parentSize or p.size()
x = parentSize.width() - infoBar.width() - self.margin
y = self.margin
index = self.infoBars[p].index(infoBar)
for bar in self.infoBars[p][0:index]:
y += (bar.height() + self.spacing)
return QPoint(x, y)
def _slideStartPos(self, infoBar: InfoBar):
return QPoint(infoBar.parent().width(), self._pos(infoBar).y())
class BottomRightInfoBarManager(InfoBarManager):
""" Bottom right position info bar manager """
def _pos(self, infoBar: InfoBar, parentSize=None) -> QPoint:
p = infoBar.parent()
parentSize = parentSize or p.size()
x = parentSize.width() - infoBar.width() - self.margin
y = parentSize.height() - infoBar.height() - self.margin
index = self.infoBars[p].index(infoBar)
for bar in self.infoBars[p][0:index]:
y -= (bar.height() + self.spacing)
return QPoint(x, y)
def _slideStartPos(self, infoBar: InfoBar):
return QPoint(infoBar.parent().width(), self._pos(infoBar).y())
class TopLeftInfoBarManager(InfoBarManager):
""" Top left position info bar manager """
def _pos(self, infoBar: InfoBar, parentSize=None) -> QPoint:
p = infoBar.parent()
parentSize = parentSize or p.size()
y = self.margin
index = self.infoBars[p].index(infoBar)
for bar in self.infoBars[p][0:index]:
y += (bar.height() + self.spacing)
return QPoint(self.margin, y)
def _slideStartPos(self, infoBar: InfoBar):
return QPoint(-infoBar.width(), self._pos(infoBar).y())
class BottomLeftInfoBarManager(InfoBarManager):
""" Bottom left position info bar manager """
def _pos(self, infoBar: InfoBar, parentSize: QSize = None) -> QPoint:
p = infoBar.parent()
parentSize = parentSize or p.size()
y = parentSize.height() - infoBar.height() - self.margin
index = self.infoBars[p].index(infoBar)
for bar in self.infoBars[p][0:index]:
y -= (bar.height() + self.spacing)
return QPoint(self.margin, y)
def _slideStartPos(self, infoBar: InfoBar):
return QPoint(-infoBar.width(), self._pos(infoBar).y())
class BottomInfoBarManager(InfoBarManager):
""" Bottom position info bar manager """
def _pos(self, infoBar: InfoBar, parentSize: QSize = None) -> QPoint:
p = infoBar.parent()
parentSize = parentSize or p.size()
x = (parentSize.width() - infoBar.width()) // 2
y = parentSize.height() - infoBar.height() - self.margin
index = self.infoBars[p].index(infoBar)
for bar in self.infoBars[p][0:index]:
y -= (bar.height() + self.spacing)
return QPoint(x, y)
def _slideStartPos(self, infoBar: InfoBar):
pos = self._pos(infoBar)
return QPoint(pos.x(), pos.y() + 16)
...@@ -6,7 +6,7 @@ with open('README.md', encoding='utf-8') as f: ...@@ -6,7 +6,7 @@ with open('README.md', encoding='utf-8') as f:
setuptools.setup( setuptools.setup(
name="PyQt-Fluent-Widgets", name="PyQt-Fluent-Widgets",
version="0.4.8", version="0.4.9",
keywords="pyqt fluent widgets", keywords="pyqt fluent widgets",
author="zhiyiYo", author="zhiyiYo",
author_email="shokokawaii@outlook.com", author_email="shokokawaii@outlook.com",
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册