Files
fluent_widgets_pyside6/qfluentwidgets/components/widgets/button.py

1059 lines
33 KiB
Python
Raw Normal View History

2025-08-14 18:45:16 +08:00
# coding:utf-8
from typing import Union
from PySide6.QtCore import Signal, QUrl, Qt, QRectF, QSize, QPoint, Property, QRect
from PySide6.QtGui import QDesktopServices, QIcon, QPainter, QColor, QPainterPath
from PySide6.QtWidgets import QHBoxLayout, QPushButton, QRadioButton, QToolButton, QApplication, QWidget, QSizePolicy
from ...common.animation import TranslateYAnimation
from ...common.icon import FluentIconBase, drawIcon, isDarkTheme, Theme, toQIcon, Icon
from ...common.icon import FluentIcon as FIF
from ...common.font import setFont, getFont
from ...common.style_sheet import FluentStyleSheet, themeColor, ThemeColor
from ...common.color import autoFallbackThemeColor
from ...common.overload import singledispatchmethod
from .menu import RoundMenu, MenuAnimationType
class PushButton(QPushButton):
""" Push button
Constructors
------------
* PushButton(`parent`: QWidget = None)
* PushButton(`text`: str, `parent`: QWidget = None, `icon`: QIcon | str | FluentIconBase = None)
* PushButton(`icon`: QIcon | FluentIcon, `text`: str, `parent`: QWidget = None)
"""
@singledispatchmethod
def __init__(self, parent: QWidget = None):
super().__init__(parent)
FluentStyleSheet.BUTTON.apply(self)
self.isPressed = False
self.isHover = False
self.setIconSize(QSize(16, 16))
self.setIcon(None)
setFont(self)
self._postInit()
@__init__.register
def _(self, text: str, parent: QWidget = None, icon: Union[QIcon, str, FluentIconBase] = None):
self.__init__(parent=parent)
self.setText(text)
self.setIcon(icon)
@__init__.register
def _(self, icon: QIcon, text: str, parent: QWidget = None):
self.__init__(text, parent, icon)
@__init__.register
def _(self, icon: FluentIconBase, text: str, parent: QWidget = None):
self.__init__(text, parent, icon)
def _postInit(self):
pass
def setIcon(self, icon: Union[QIcon, str, FluentIconBase]):
self.setProperty('hasIcon', icon is not None)
self.setStyle(QApplication.style())
self._icon = icon or QIcon()
self.update()
def icon(self):
return toQIcon(self._icon)
def setProperty(self, name: str, value) -> bool:
if name != 'icon':
return super().setProperty(name, value)
self.setIcon(value)
return True
def mousePressEvent(self, e):
self.isPressed = True
super().mousePressEvent(e)
def mouseReleaseEvent(self, e):
self.isPressed = False
super().mouseReleaseEvent(e)
def enterEvent(self, e):
self.isHover = True
self.update()
def leaveEvent(self, e):
self.isHover = False
self.update()
def _drawIcon(self, icon, painter, rect, state=QIcon.Off):
""" draw icon """
drawIcon(icon, painter, rect, state)
def paintEvent(self, e):
super().paintEvent(e)
if self.icon().isNull():
return
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing |
QPainter.SmoothPixmapTransform)
if not self.isEnabled():
painter.setOpacity(0.3628)
elif self.isPressed:
painter.setOpacity(0.786)
w, h = self.iconSize().width(), self.iconSize().height()
y = (self.height() - h) / 2
mw = self.minimumSizeHint().width()
if mw > 0:
x = 12 + (self.width() - mw) // 2
else:
x = 12
if self.isRightToLeft():
x = self.width() - w - x
self._drawIcon(self._icon, painter, QRectF(x, y, w, h))
class PrimaryPushButton(PushButton):
""" Primary color push button
Constructors
------------
* PrimaryPushButton(`parent`: QWidget = None)
* PrimaryPushButton(`text`: str, `parent`: QWidget = None, `icon`: QIcon | str | FluentIconBase = None)
* PrimaryPushButton(`icon`: QIcon | FluentIcon, `text`: str, `parent`: QWidget = None)
"""
def _drawIcon(self, icon, painter, rect, state=QIcon.Off):
if isinstance(icon, FluentIconBase) and self.isEnabled():
# reverse icon color
theme = Theme.DARK if not isDarkTheme() else Theme.LIGHT
icon = icon.icon(theme)
elif not self.isEnabled():
painter.setOpacity(0.786 if isDarkTheme() else 0.9)
if isinstance(icon, FluentIconBase):
icon = icon.icon(Theme.DARK)
PushButton._drawIcon(self, icon, painter, rect, state)
class TransparentPushButton(PushButton):
""" Transparent push button
Constructors
------------
* TransparentPushButton(`parent`: QWidget = None)
* TransparentPushButton(`text`: str, `parent`: QWidget = None, `icon`: QIcon | str | FluentIconBase = None)
* TransparentPushButton(`icon`: QIcon | FluentIcon, `text`: str, `parent`: QWidget = None)
"""
class ToggleButton(PushButton):
""" Toggle push button
Constructors
------------
* ToggleButton(`parent`: QWidget = None)
* ToggleButton(`text`: str, `parent`: QWidget = None, `icon`: QIcon | str | FluentIconBase = None)
* ToggleButton(`icon`: QIcon | FluentIcon, `text`: str, `parent`: QWidget = None)
"""
def _postInit(self):
self.setCheckable(True)
self.setChecked(False)
def _drawIcon(self, icon, painter, rect):
if not self.isChecked():
return PushButton._drawIcon(self, icon, painter, rect)
PrimaryPushButton._drawIcon(self, icon, painter, rect, QIcon.On)
TogglePushButton = ToggleButton
class TransparentTogglePushButton(TogglePushButton):
""" Transparent toggle push button
Constructors
------------
* TransparentTogglePushButton(`parent`: QWidget = None)
* TransparentTogglePushButton(`text`: str, `parent`: QWidget = None,
`icon`: QIcon | str | FluentIconBase = None)
* TransparentTogglePushButton(`icon`: QIcon | FluentIconBase, `text`: str, `parent`: QWidget = None)
"""
class HyperlinkButton(PushButton):
""" Hyperlink button
Constructors
------------
* HyperlinkButton(`parent`: QWidget = None)
* HyperlinkButton(`url`: str, `text`: str, `parent`: QWidget = None, `icon`: QIcon | str | FluentIconBase = None)
* HyperlinkButton(`icon`: QIcon | FluentIconBase, `url`: str, `text`: str, `parent`: QWidget = None)
"""
@singledispatchmethod
def __init__(self, parent: QWidget = None):
super().__init__(parent)
self._url = QUrl()
FluentStyleSheet.BUTTON.apply(self)
self.setCursor(Qt.PointingHandCursor)
setFont(self)
self.clicked.connect(self._onClicked)
@__init__.register
def _(self, url: str, text: str, parent: QWidget = None, icon: Union[QIcon, FluentIconBase, str] = None):
self.__init__(parent)
self.setText(text)
self.url.setUrl(url)
self.setIcon(icon)
@__init__.register
def _(self, icon: QIcon, url: str, text: str, parent: QWidget = None):
self.__init__(url, text, parent, icon)
@__init__.register
def _(self, icon: FluentIconBase, url: str, text: str, parent: QWidget = None):
self.__init__(url, text, parent, icon)
def getUrl(self):
return self._url
def setUrl(self, url: Union[str, QUrl]):
self._url = QUrl(url)
def _onClicked(self):
if self.getUrl().isValid():
QDesktopServices.openUrl(self.getUrl())
def _drawIcon(self, icon, painter, rect, state=QIcon.Off):
if isinstance(icon, FluentIconBase) and self.isEnabled():
icon = icon.icon(color=themeColor())
elif not self.isEnabled():
painter.setOpacity(0.3628 if isDarkTheme() else 0.36)
drawIcon(icon, painter, rect, state)
url = Property(QUrl, getUrl, setUrl)
class RadioButton(QRadioButton):
""" Radio button
Constructors
------------
* RadioButton(`parent`: QWidget = None)
* RadioButton(`url`: text, `text`: str, `parent`: QWidget = None,
`icon`: QIcon | str | FluentIconBase = None)
"""
@singledispatchmethod
def __init__(self, parent: QWidget = None):
super().__init__(parent)
self._lightTextColor = QColor(0, 0, 0)
self._darkTextColor = QColor(255, 255, 255)
self.lightIndicatorColor = QColor()
self.darkIndicatorColor = QColor()
self.indicatorPos = QPoint(11, 12)
self.isHover = False
FluentStyleSheet.BUTTON.apply(self)
self.setAttribute(Qt.WA_MacShowFocusRect, False)
self._postInit()
@__init__.register
def _(self, text: str, parent: QWidget = None):
self.__init__(parent)
self.setText(text)
def _postInit(self):
pass
def enterEvent(self, e):
self.isHover = True
self.update()
def leaveEvent(self, e):
self.isHover = False
self.update()
def paintEvent(self, e):
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing)
self._drawIndicator(painter)
self._drawText(painter)
def _drawText(self, painter: QPainter):
if not self.isEnabled():
painter.setOpacity(0.36)
painter.setFont(self.font())
painter.setPen(self.textColor())
painter.drawText(QRect(29, 0, self.width(), self.height()), Qt.AlignVCenter, self.text())
def _drawIndicator(self, painter: QPainter):
if self.isChecked():
if self.isEnabled():
borderColor = autoFallbackThemeColor(self.lightIndicatorColor, self.darkIndicatorColor)
else:
borderColor = QColor(255, 255, 255, 40) if isDarkTheme() else QColor(0, 0, 0, 55)
filledColor = Qt.black if isDarkTheme() else Qt.white
if self.isHover and not self.isDown():
self._drawCircle(painter, self.indicatorPos, 10, 4, borderColor, filledColor)
else:
self._drawCircle(painter, self.indicatorPos, 10, 5, borderColor, filledColor)
else:
if self.isEnabled():
if not self.isDown():
borderColor = QColor(255, 255, 255, 153) if isDarkTheme() else QColor(0, 0, 0, 153)
else:
borderColor = QColor(255, 255, 255, 40) if isDarkTheme() else QColor(0, 0, 0, 55)
if self.isDown():
filledColor = Qt.black if isDarkTheme() else Qt.white
elif self.isHover:
filledColor = QColor(255, 255, 255, 11) if isDarkTheme() else QColor(0, 0, 0, 15)
else:
filledColor = QColor(0, 0, 0, 26) if isDarkTheme() else QColor(0, 0, 0, 6)
else:
filledColor = Qt.transparent
borderColor = QColor(255, 255, 255, 40) if isDarkTheme() else QColor(0, 0, 0, 55)
self._drawCircle(painter, self.indicatorPos, 10, 1, borderColor, filledColor)
if self.isEnabled() and self.isDown():
borderColor = QColor(255, 255, 255, 40) if isDarkTheme() else QColor(0, 0, 0, 24)
self._drawCircle(painter, self.indicatorPos, 9, 4, borderColor, Qt.transparent)
def _drawCircle(self, painter: QPainter, center: QPoint, radius, thickness, borderColor, filledColor):
path = QPainterPath()
path.setFillRule(Qt.FillRule.WindingFill)
# outer circle (border)
outerRect = QRectF(center.x() - radius, center.y() - radius, 2 * radius, 2 * radius)
path.addEllipse(outerRect)
# inner center (filled)
ir = radius - thickness
innerRect = QRectF(center.x() - ir, center.y() - ir, 2 * ir, 2 * ir)
innerPath = QPainterPath()
innerPath.addEllipse(innerRect)
path = path.subtracted(innerPath)
# draw outer ring
painter.setPen(Qt.NoPen)
painter.fillPath(path, borderColor)
# fill inner circle
painter.fillPath(innerPath, filledColor)
def textColor(self):
return self.darkTextColor if isDarkTheme() else self.lightTextColor
def getLightTextColor(self) -> QColor:
return self._lightTextColor
def getDarkTextColor(self) -> QColor:
return self._darkTextColor
def setLightTextColor(self, color: QColor):
self._lightTextColor = QColor(color)
self.update()
def setDarkTextColor(self, color: QColor):
self._darkTextColor = QColor(color)
self.update()
def setIndicatorColor(self, light, dark):
self.lightIndicatorColor = QColor(light)
self.darkIndicatorColor = QColor(dark)
self.update()
def setTextColor(self, light, dark):
self.setLightTextColor(light)
self.setDarkTextColor(dark)
lightTextColor = Property(QColor, getLightTextColor, setLightTextColor)
darkTextColor = Property(QColor, getDarkTextColor, setDarkTextColor)
class ToolButton(QToolButton):
""" Tool button
Constructors
------------
* ToolButton(`parent`: QWidget = None)
* ToolButton(`icon`: QIcon | str | FluentIconBase, `parent`: QWidget = None)
"""
@singledispatchmethod
def __init__(self, parent: QWidget = None):
super().__init__(parent)
FluentStyleSheet.BUTTON.apply(self)
self.isPressed = False
self.isHover = False
self.setIconSize(QSize(16, 16))
self.setIcon(QIcon())
setFont(self)
self._postInit()
@__init__.register
def _(self, icon: FluentIconBase, parent: QWidget = None):
self.__init__(parent)
self.setIcon(icon)
@__init__.register
def _(self, icon: QIcon, parent: QWidget = None):
self.__init__(parent)
self.setIcon(icon)
@__init__.register
def _(self, icon: str, parent: QWidget = None):
self.__init__(parent)
self.setIcon(icon)
def _postInit(self):
pass
def setIcon(self, icon: Union[QIcon, str, FluentIconBase]):
self._icon = icon
self.update()
def icon(self):
return toQIcon(self._icon)
def setProperty(self, name: str, value) -> bool:
if name != 'icon':
return super().setProperty(name, value)
self.setIcon(value)
return True
def mousePressEvent(self, e):
self.isPressed = True
super().mousePressEvent(e)
def mouseReleaseEvent(self, e):
self.isPressed = False
super().mouseReleaseEvent(e)
def enterEvent(self, e):
self.isHover = True
self.update()
def leaveEvent(self, e):
self.isHover = False
self.update()
def _drawIcon(self, icon, painter: QPainter, rect: QRectF, state=QIcon.Off):
""" draw icon """
drawIcon(icon, painter, rect, state)
def paintEvent(self, e):
super().paintEvent(e)
if self._icon is None:
return
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing |
QPainter.SmoothPixmapTransform)
if not self.isEnabled():
painter.setOpacity(0.43)
elif self.isPressed:
painter.setOpacity(0.63)
w, h = self.iconSize().width(), self.iconSize().height()
y = (self.height() - h) / 2
x = (self.width() - w) / 2
self._drawIcon(self._icon, painter, QRectF(x, y, w, h))
class TransparentToolButton(ToolButton):
""" Transparent background tool button
Constructors
------------
* TransparentToolButton(`parent`: QWidget = None)
* TransparentToolButton(`icon`: QIcon | str | FluentIconBase, `parent`: QWidget = None)
"""
class PrimaryToolButton(ToolButton):
""" Primary color tool button
Constructors
------------
* PrimaryToolButton(`parent`: QWidget = None)
* PrimaryToolButton(`icon`: QIcon | str | FluentIconBase, `parent`: QWidget = None)
"""
def _drawIcon(self, icon, painter: QPainter, rect: QRectF, state=QIcon.Off):
if isinstance(icon, FluentIconBase) and self.isEnabled():
# reverse icon color
theme = Theme.DARK if not isDarkTheme() else Theme.LIGHT
icon = icon.icon(theme)
elif isinstance(icon, Icon) and self.isEnabled():
theme = Theme.DARK if not isDarkTheme() else Theme.LIGHT
icon = icon.fluentIcon.icon(theme)
elif not self.isEnabled():
painter.setOpacity(0.786 if isDarkTheme() else 0.9)
if isinstance(icon, FluentIconBase):
icon = icon.icon(Theme.DARK)
return drawIcon(icon, painter, rect, state)
class ToggleToolButton(ToolButton):
""" Toggle tool button
Constructors
------------
* ToggleToolButton(`parent`: QWidget = None)
* ToggleToolButton(`icon`: QIcon | str | FluentIconBase, `parent`: QWidget = None)
"""
def _postInit(self):
self.setCheckable(True)
self.setChecked(False)
def _drawIcon(self, icon, painter, rect):
if not self.isChecked():
return ToolButton._drawIcon(self, icon, painter, rect)
PrimaryToolButton._drawIcon(self, icon, painter, rect, QIcon.On)
class TransparentToggleToolButton(ToggleToolButton):
""" Transparent toggle tool button
Constructors
------------
* TransparentToggleToolButton(`parent`: QWidget = None)
* TransparentToggleToolButton(`icon`: QIcon | str | FluentIconBase, `parent`: QWidget = None)
"""
class DropDownButtonBase:
""" Drop down button base class """
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._menu = None
self.arrowAni = TranslateYAnimation(self)
def setMenu(self, menu: RoundMenu):
self._menu = menu
def menu(self) -> RoundMenu:
return self._menu
def _showMenu(self):
if not self.menu():
return
menu = self.menu()
menu.view.setMinimumWidth(self.width())
menu.view.adjustSize()
menu.adjustSize()
# determine the animation type by choosing the maximum height of view
x = -menu.width()//2 + menu.layout().contentsMargins().left() + self.width()//2
pd = self.mapToGlobal(QPoint(x, self.height()))
hd = menu.view.heightForAnimation(pd, MenuAnimationType.DROP_DOWN)
pu = self.mapToGlobal(QPoint(x, 0))
hu = menu.view.heightForAnimation(pu, MenuAnimationType.PULL_UP)
if hd >= hu:
menu.view.adjustSize(pd, MenuAnimationType.DROP_DOWN)
menu.exec(pd, aniType=MenuAnimationType.DROP_DOWN)
else:
menu.view.adjustSize(pu, MenuAnimationType.PULL_UP)
menu.exec(pu, aniType=MenuAnimationType.PULL_UP)
def _hideMenu(self):
if self.menu():
self.menu().hide()
def _drawDropDownIcon(self, painter, rect):
if isDarkTheme():
FIF.ARROW_DOWN.render(painter, rect)
else:
FIF.ARROW_DOWN.render(painter, rect, fill="#646464")
def paintEvent(self, e):
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing)
if self.isHover:
painter.setOpacity(0.8)
elif self.isPressed:
painter.setOpacity(0.7)
rect = QRectF(self.width()-22, self.height() /
2-5+self.arrowAni.y, 10, 10)
self._drawDropDownIcon(painter, rect)
class DropDownPushButton(DropDownButtonBase, PushButton):
""" Drop down push button
Constructors
------------
* DropDownPushButton(`parent`: QWidget = None)
* DropDownPushButton(`text`: str, `parent`: QWidget = None,
`icon`: QIcon | str | FluentIconBase = None)
* DropDownPushButton(`icon`: QIcon | FluentIcon, `text`: str, `parent`: QWidget = None)
"""
def mouseReleaseEvent(self, e):
PushButton.mouseReleaseEvent(self, e)
self._showMenu()
def paintEvent(self, e):
PushButton.paintEvent(self, e)
DropDownButtonBase.paintEvent(self, e)
class TransparentDropDownPushButton(DropDownPushButton):
""" Transparent drop down push button
Constructors
------------
* TransparentDropDownPushButton(`parent`: QWidget = None)
* TransparentDropDownPushButton(`text`: str, `parent`: QWidget = None,
`icon`: QIcon | str | FluentIconBase = None)
* TransparentDropDownPushButton(`icon`: QIcon | FluentIcon, `text`: str, `parent`: QWidget = None)
"""
class DropDownToolButton(DropDownButtonBase, ToolButton):
""" Drop down tool button
Constructors
------------
* DropDownToolButton(`parent`: QWidget = None)
* DropDownToolButton(`icon`: QIcon | str | FluentIconBase, `parent`: QWidget = None)
"""
def mouseReleaseEvent(self, e):
ToolButton.mouseReleaseEvent(self, e)
self._showMenu()
def _drawIcon(self, icon, painter, rect: QRectF):
rect.moveLeft(12)
return super()._drawIcon(icon, painter, rect)
def paintEvent(self, e):
ToolButton.paintEvent(self, e)
DropDownButtonBase.paintEvent(self, e)
class TransparentDropDownToolButton(DropDownToolButton):
""" Transparent drop down tool button
Constructors
------------
* TransparentDropDownToolButton(`parent`: QWidget = None)
* TransparentDropDownToolButton(`icon`: QIcon | str | FluentIconBase, `parent`: QWidget = None)
"""
class PrimaryDropDownButtonBase(DropDownButtonBase):
""" Primary color drop down button base class """
def _drawDropDownIcon(self, painter, rect):
theme = Theme.DARK if not isDarkTheme() else Theme.LIGHT
FIF.ARROW_DOWN.render(painter, rect, theme)
class PrimaryDropDownPushButton(PrimaryDropDownButtonBase, PrimaryPushButton):
""" Primary color drop down push button
Constructors
------------
* PrimaryDropDownPushButton(`parent`: QWidget = None)
* PrimaryDropDownPushButton(`text`: str, `parent`: QWidget = None,
`icon`: QIcon | str | FluentIconBase = None)
* PrimaryDropDownPushButton(`icon`: QIcon | FluentIcon, `text`: str, `parent`: QWidget = None)
"""
def mouseReleaseEvent(self, e):
PrimaryPushButton.mouseReleaseEvent(self, e)
self._showMenu()
def paintEvent(self, e):
PrimaryPushButton.paintEvent(self, e)
PrimaryDropDownButtonBase.paintEvent(self, e)
class PrimaryDropDownToolButton(PrimaryDropDownButtonBase, PrimaryToolButton):
""" Primary drop down tool button
Constructors
------------
* PrimaryDropDownToolButton(`parent`: QWidget = None)
* PrimaryDropDownToolButton(`icon`: QIcon | str | FluentIconBase, `parent`: QWidget = None)
"""
def mouseReleaseEvent(self, e):
PrimaryToolButton.mouseReleaseEvent(self, e)
self._showMenu()
def _drawIcon(self, icon, painter, rect: QRectF):
rect.moveLeft(12)
return super()._drawIcon(icon, painter, rect)
def paintEvent(self, e):
PrimaryToolButton.paintEvent(self, e)
PrimaryDropDownButtonBase.paintEvent(self, e)
class SplitDropButton(ToolButton):
def _postInit(self):
self.arrowAni = TranslateYAnimation(self)
self.setIcon(FIF.ARROW_DOWN)
self.setIconSize(QSize(10, 10))
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding)
def _drawIcon(self, icon, painter, rect):
rect.translate(0, self.arrowAni.y)
if self.isPressed:
painter.setOpacity(0.5)
elif self.isHover:
painter.setOpacity(1)
else:
painter.setOpacity(0.63)
super()._drawIcon(icon, painter, rect)
class PrimarySplitDropButton(PrimaryToolButton):
def _postInit(self):
self.arrowAni = TranslateYAnimation(self)
self.setIcon(FIF.ARROW_DOWN)
self.setIconSize(QSize(10, 10))
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding)
def _drawIcon(self, icon, painter, rect):
rect.translate(0, self.arrowAni.y)
if self.isPressed:
painter.setOpacity(0.7)
elif self.isHover:
painter.setOpacity(0.9)
else:
painter.setOpacity(1)
if isinstance(icon, FluentIconBase):
icon = icon.icon(Theme.DARK if not isDarkTheme() else Theme.LIGHT)
super()._drawIcon(icon, painter, rect)
class SplitWidgetBase(QWidget):
""" Split widget base class """
dropDownClicked = Signal()
def __init__(self, parent=None):
super().__init__(parent=parent)
self.flyout = None # type: QWidget
self.dropButton = SplitDropButton(self)
self.hBoxLayout = QHBoxLayout(self)
self.hBoxLayout.setSpacing(0)
self.hBoxLayout.setContentsMargins(0, 0, 0, 0)
self.hBoxLayout.addWidget(self.dropButton)
self.dropButton.clicked.connect(self.dropDownClicked)
self.dropButton.clicked.connect(self.showFlyout)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
def setWidget(self, widget: QWidget):
""" set the widget on left side """
self.hBoxLayout.insertWidget(0, widget, 1, Qt.AlignLeft)
def setDropButton(self, button):
""" set drop dow button """
self.hBoxLayout.removeWidget(self.dropButton)
self.dropButton.deleteLater()
self.dropButton = button
self.dropButton.clicked.connect(self.dropDownClicked)
self.dropButton.clicked.connect(self.showFlyout)
self.hBoxLayout.addWidget(button)
def setDropIcon(self, icon: Union[str, QIcon, FluentIconBase]):
""" set the icon of drop down button """
self.dropButton.setIcon(icon)
self.dropButton.removeEventFilter(self.dropButton.arrowAni)
def setDropIconSize(self, size: QSize):
""" set the icon size of drop down button """
self.dropButton.setIconSize(size)
def setFlyout(self, flyout):
""" set the widget pops up when drop down button is clicked
Parameters
----------
flyout: QWidget
the widget pops up when drop down button is clicked.
It should contain `exec(pos: QPoint)` method
"""
self.flyout = flyout
def showFlyout(self):
""" show flyout """
if not self.flyout:
return
w = self.flyout
if isinstance(w, RoundMenu):
w.view.setMinimumWidth(self.width())
w.view.adjustSize()
w.adjustSize()
dx = w.layout().contentsMargins().left() if isinstance(w, RoundMenu) else 0
x = -w.width()//2 + dx + self.width()//2
y = self.height()
w.exec(self.mapToGlobal(QPoint(x, y)))
class SplitPushButton(SplitWidgetBase):
""" Split push button
Constructors
------------
* SplitPushButton(`parent`: QWidget = None)
* SplitPushButton(`text`: str, `parent`: QWidget = None, `icon`: QIcon | str | FluentIconBase = None)
"""
clicked = Signal()
@singledispatchmethod
def __init__(self, parent: QWidget = None):
super().__init__(parent=parent)
self.button = PushButton(self)
self.button.setObjectName('splitPushButton')
self.button.clicked.connect(self.clicked)
self.setWidget(self.button)
self._postInit()
@__init__.register
def _(self, text: str, parent: QWidget = None, icon: Union[QIcon, str, FluentIconBase] = None):
self.__init__(parent)
self.setText(text)
self.setIcon(icon)
@__init__.register
def _(self, icon: QIcon, text: str, parent: QWidget = None):
self.__init__(text, parent, icon)
@__init__.register
def _(self, icon: FluentIconBase, text: str, parent: QWidget = None):
self.__init__(text, parent, icon)
def _postInit(self):
pass
def text(self):
return self.button.text()
def setText(self, text: str):
self.button.setText(text)
self.adjustSize()
def icon(self):
return self.button.icon()
def setIcon(self, icon: Union[QIcon, FluentIconBase, str]):
self.button.setIcon(icon)
def setIconSize(self, size: QSize):
self.button.setIconSize(size)
text_ = Property(str, text, setText)
icon_ = Property(QIcon, icon, setIcon)
class PrimarySplitPushButton(SplitPushButton):
""" Primary split push button
Constructors
------------
* PrimarySplitPushButton(`parent`: QWidget = None)
* PrimarySplitPushButton(`text`: str, `parent`: QWidget = None,
`icon`: QIcon | str | FluentIconBase = None)
* PrimarySplitPushButton(`icon`: QIcon | FluentIcon, `text`: str, `parent`: QWidget = None)
"""
def _postInit(self):
self.setDropButton(PrimarySplitDropButton(self))
self.hBoxLayout.removeWidget(self.button)
self.button.deleteLater()
self.button = PrimaryPushButton(self)
self.button.setObjectName('primarySplitPushButton')
self.button.clicked.connect(self.clicked)
self.setWidget(self.button)
class SplitToolButton(SplitWidgetBase):
""" Split tool button
Constructors
------------
* SplitToolButton(`parent`: QWidget = None)
* SplitToolButton(`icon`: QIcon | str | FluentIconBase, `parent`: QWidget = None)
"""
clicked = Signal()
@singledispatchmethod
def __init__(self, parent: QWidget = None):
super().__init__(parent=parent)
self.button = ToolButton(self)
self.button.setObjectName('splitToolButton')
self.button.clicked.connect(self.clicked)
self.setWidget(self.button)
self._postInit()
@__init__.register
def _(self, icon: FluentIconBase, parent: QWidget = None):
self.__init__(parent)
self.setIcon(icon)
@__init__.register
def _(self, icon: QIcon, parent: QWidget = None):
self.__init__(parent)
self.setIcon(icon)
@__init__.register
def _(self, icon: str, parent: QWidget = None):
self.__init__(parent)
self.setIcon(icon)
def _postInit(self):
pass
def icon(self):
return self.button.icon()
def setIcon(self, icon: Union[QIcon, FluentIconBase, str]):
self.button.setIcon(icon)
def setIconSize(self, size: QSize):
self.button.setIconSize(size)
icon_ = Property(QIcon, icon, setIcon)
class PrimarySplitToolButton(SplitToolButton):
""" Primary split push button
Constructors
------------
* PrimarySplitToolButton(`parent`: QWidget = None)
* PrimarySplitToolButton(`icon`: QIcon | str | FluentIconBase, `parent`: QWidget = None)
"""
def _postInit(self):
self.setDropButton(PrimarySplitDropButton(self))
self.hBoxLayout.removeWidget(self.button)
self.button.deleteLater()
self.button = PrimaryToolButton(self)
self.button.setObjectName('primarySplitToolButton')
self.button.clicked.connect(self.clicked)
self.setWidget(self.button)
class PillButtonBase:
""" Pill button base class """
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def paintEvent(self, e):
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing)
isDark = isDarkTheme()
if not self.isChecked():
rect = self.rect().adjusted(1, 1, -1, -1)
borderColor = QColor(255, 255, 255, 18) if isDark else QColor(0, 0, 0, 15)
if not self.isEnabled():
bgColor = QColor(255, 255, 255, 11) if isDark else QColor(249, 249, 249, 75)
elif self.isPressed or self.isHover:
bgColor = QColor(255, 255, 255, 21) if isDark else QColor(249, 249, 249, 128)
else:
bgColor = QColor(255, 255, 255, 15) if isDark else QColor(243, 243, 243, 194)
else:
if not self.isEnabled():
bgColor = QColor(255, 255, 255, 40) if isDark else QColor(0, 0, 0, 55)
elif self.isPressed:
bgColor = ThemeColor.DARK_2.color() if isDark else ThemeColor.LIGHT_3.color()
elif self.isHover:
bgColor = ThemeColor.DARK_1.color() if isDark else ThemeColor.LIGHT_1.color()
else:
bgColor = themeColor()
borderColor = Qt.transparent
rect = self.rect()
painter.setPen(borderColor)
painter.setBrush(bgColor)
r = rect.height() / 2
painter.drawRoundedRect(rect, r, r)
class PillPushButton(TogglePushButton, PillButtonBase):
""" Pill push button
Constructors
------------
* PillPushButton(`parent`: QWidget = None)
* PillPushButton(`text`: str, `parent`: QWidget = None,
`icon`: QIcon | str | FluentIconBase = None)
* PillPushButton(`icon`: QIcon | FluentIcon, `text`: str, `parent`: QWidget = None)
"""
def paintEvent(self, e):
PillButtonBase.paintEvent(self, e)
TogglePushButton.paintEvent(self, e)
class PillToolButton(ToggleToolButton, PillButtonBase):
""" Pill push button
Constructors
------------
* PillToolButton(`parent`: QWidget = None)
* PillToolButton(`icon`: QIcon | str | FluentIconBase, `parent`: QWidget = None)
"""
def paintEvent(self, e):
PillButtonBase.paintEvent(self, e)
ToggleToolButton.paintEvent(self, e)