You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
608 lines
17 KiB
608 lines
17 KiB
# coding:utf-8
|
|
from typing import Union
|
|
|
|
from PySide6.QtCore import Signal, QUrl, Qt, QRectF, QSize, QPoint, Property
|
|
from PySide6.QtGui import QDesktopServices, QIcon, QPainter, QFont
|
|
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
|
|
from ...common.icon import FluentIcon as FIF
|
|
from ...common.font import setFont
|
|
from ...common.style_sheet import FluentStyleSheet
|
|
from ...common.overload import singledispatchmethod
|
|
from .menu import RoundMenu
|
|
|
|
|
|
class PushButton(QPushButton):
|
|
""" push button """
|
|
|
|
@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)
|
|
|
|
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):
|
|
""" draw icon """
|
|
drawIcon(icon, painter, rect)
|
|
|
|
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:
|
|
self._drawIcon(self._icon, painter, QRectF(
|
|
12+(self.width()-mw)//2, y, w, h))
|
|
else:
|
|
self._drawIcon(self._icon, painter, QRectF(12, y, w, h))
|
|
|
|
|
|
class PrimaryPushButton(PushButton):
|
|
""" Primary color push button """
|
|
|
|
def _drawIcon(self, icon, painter, rect):
|
|
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)
|
|
icon = icon.icon(Theme.DARK)
|
|
|
|
PushButton._drawIcon(self, icon, painter, rect)
|
|
|
|
|
|
class ToggleButton(PushButton):
|
|
|
|
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)
|
|
|
|
|
|
class HyperlinkButton(QPushButton):
|
|
""" Hyperlink button """
|
|
|
|
@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(lambda i: QDesktopServices.openUrl(self.getUrl()))
|
|
|
|
@__init__.register
|
|
def _(self, url: str, text: str, parent: QWidget = None):
|
|
self.__init__(parent)
|
|
self.setText(text)
|
|
self.url.setUrl(url)
|
|
|
|
def getUrl(self):
|
|
return self._url
|
|
|
|
def setUrl(self, url: Union[str, QUrl]):
|
|
self._url = QUrl(url)
|
|
|
|
url = Property(QUrl, getUrl, setUrl)
|
|
|
|
|
|
class RadioButton(QRadioButton):
|
|
""" Radio button """
|
|
|
|
@singledispatchmethod
|
|
def __init__(self, parent: QWidget = None):
|
|
super().__init__(parent)
|
|
FluentStyleSheet.BUTTON.apply(self)
|
|
|
|
@__init__.register
|
|
def _(self, text: str, parent: QWidget = None):
|
|
self.__init__(parent)
|
|
self.setText(text)
|
|
|
|
|
|
class ToolButton(QToolButton):
|
|
""" Tool button """
|
|
|
|
@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):
|
|
""" draw icon """
|
|
drawIcon(icon, painter, rect)
|
|
|
|
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 """
|
|
|
|
|
|
class PrimaryToolButton(ToolButton):
|
|
""" Primary color tool button """
|
|
|
|
def _drawIcon(self, icon, painter: QPainter, rect: QRectF):
|
|
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)
|
|
icon = icon.icon(Theme.DARK)
|
|
|
|
return drawIcon(icon, painter, rect)
|
|
|
|
|
|
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()
|
|
|
|
if menu.view.width() < self.width():
|
|
menu.view.setMinimumWidth(self.width())
|
|
menu.adjustSize()
|
|
|
|
# show menu
|
|
x = -menu.width()//2 + menu.layout().contentsMargins().left() + self.width()//2
|
|
y = self.height()
|
|
menu.exec(self.mapToGlobal(QPoint(x, y)))
|
|
|
|
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 """
|
|
|
|
def mouseReleaseEvent(self, e):
|
|
PushButton.mouseReleaseEvent(self, e)
|
|
self._showMenu()
|
|
|
|
def paintEvent(self, e):
|
|
PushButton.paintEvent(self, e)
|
|
DropDownButtonBase.paintEvent(self, e)
|
|
|
|
|
|
class DropDownToolButton(DropDownButtonBase, ToolButton):
|
|
""" Drop down tool button """
|
|
|
|
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 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 """
|
|
|
|
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 """
|
|
|
|
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.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(FIF.ARROW_DOWN, painter, rect)
|
|
|
|
|
|
class PrimarySplitDropButton(PrimaryToolButton):
|
|
|
|
def _postInit(self):
|
|
self.arrowAni = TranslateYAnimation(self)
|
|
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)
|
|
|
|
theme = Theme.DARK if not isDarkTheme() else Theme.LIGHT
|
|
super()._drawIcon(FIF.ARROW_DOWN.icon(theme), 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 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 the `exec` method, whose first parameter type is `QPoint`
|
|
"""
|
|
self.flyout = flyout
|
|
|
|
def showFlyout(self):
|
|
""" show flyout """
|
|
if not self.flyout:
|
|
return
|
|
|
|
w = self.flyout
|
|
|
|
if isinstance(w, RoundMenu) and w.view.width() < self.width():
|
|
w.view.setMinimumWidth(self.width())
|
|
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 """
|
|
|
|
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)
|
|
|
|
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 """
|
|
|
|
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 """
|
|
|
|
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 """
|
|
|
|
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)
|