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.

270 lines
8.5 KiB

# coding: utf-8
from enum import Enum
from PySide6.QtCore import Qt, QTimer, Property, Signal, QEvent, QPoint
from PySide6.QtGui import QColor, QPainter, QHoverEvent
from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QToolButton, QWidget
from ...common.style_sheet import FluentStyleSheet
from ...common.overload import singledispatchmethod
class Indicator(QToolButton):
""" Indicator of switch button """
checkedChanged = Signal(bool)
def __init__(self, parent):
super().__init__(parent=parent)
self.setCheckable(True)
super().setChecked(False)
self.resize(37, 16)
self.__sliderOnColor = QColor(Qt.white)
self.__sliderOffColor = QColor(Qt.black)
self.__sliderDisabledColor = QColor(QColor(155, 154, 153))
self.timer = QTimer(self)
self.padding = self.height()//4
self.sliderX = self.padding
self.sliderRadius = (self.height()-2*self.padding)//2
self.sliderEndX = self.width()-2*self.sliderRadius
self.sliderStep = self.width()/50
self.timer.timeout.connect(self.__updateSliderPos)
def __updateSliderPos(self):
""" update slider position """
if self.isChecked():
if self.sliderX+self.sliderStep < self.sliderEndX:
self.sliderX += self.sliderStep
else:
self.sliderX = self.sliderEndX
self.timer.stop()
else:
if self.sliderX-self.sliderStep > self.sliderEndX:
self.sliderX -= self.sliderStep
else:
self.sliderX = self.padding
self.timer.stop()
self.style().polish(self)
def setChecked(self, isChecked: bool):
""" set checked state """
if isChecked == self.isChecked():
return
super().setChecked(isChecked)
self.sliderRadius = (self.height()-2*self.padding)//2
self.sliderEndX = self.width()-2*self.sliderRadius - \
self.padding if isChecked else self.padding
self.timer.start(5)
def toggle(self):
self.setChecked(not self.isChecked())
def mouseReleaseEvent(self, e):
""" toggle checked state when mouse release"""
super().mouseReleaseEvent(e)
self.sliderEndX = self.width()-2*self.sliderRadius - \
self.padding if self.isChecked() else self.padding
self.timer.start(5)
self.checkedChanged.emit(self.isChecked())
def resizeEvent(self, e):
self.padding = self.height()//4
self.sliderRadius = (self.height()-2*self.padding)//2
self.sliderStep = self.width()/50
self.sliderEndX = self.width()-2*self.sliderRadius - \
self.padding if self.isChecked() else self.padding
self.update()
def paintEvent(self, e):
""" paint indicator """
# the background and border are specified by qss
super().paintEvent(e)
painter = QPainter(self)
painter.setRenderHints(QPainter.Antialiasing)
painter.setPen(Qt.NoPen)
if self.isEnabled():
color = self.sliderOnColor if self.isChecked() else self.sliderOffColor
else:
color = self.sliderDisabledColor
painter.setBrush(color)
painter.drawEllipse(int(self.sliderX), int(self.padding),
self.sliderRadius*2, self.sliderRadius*2)
def getSliderOnColor(self):
return self.__sliderOnColor
def setSliderOnColor(self, color: QColor):
self.__sliderOnColor = color
self.update()
def getSliderOffColor(self):
return self.__sliderOffColor
def setSliderOffColor(self, color: QColor):
self.__sliderOffColor = color
self.update()
def getSliderDisabledColor(self):
return self.__sliderDisabledColor
def setSliderDisabledColor(self, color: QColor):
self.__sliderDisabledColor = color
self.update()
sliderOnColor = Property(QColor, getSliderOnColor, setSliderOnColor)
sliderOffColor = Property(QColor, getSliderOffColor, setSliderOffColor)
sliderDisabledColor = Property(
QColor, getSliderDisabledColor, setSliderDisabledColor)
class IndicatorPosition(Enum):
""" Indicator position """
LEFT = 0
RIGHT = 1
class SwitchButton(QWidget):
""" Switch button class """
checkedChanged = Signal(bool)
@singledispatchmethod
def __init__(self, parent: QWidget = None, indicatorPos=IndicatorPosition.LEFT):
"""
Parameters
----------
parent: QWidget
parent widget
indicatorPosition: IndicatorPosition
the position of indicator
"""
super().__init__(parent=parent)
self._text = self.tr('Off')
self._offText = self.tr('Off')
self._onText = self.tr('On')
self.__spacing = 12
self.indicatorPos = indicatorPos
self.hBox = QHBoxLayout(self)
self.indicator = Indicator(self)
self.label = QLabel(self._text, self)
self.__initWidget()
@__init__.register
def _(self, text: str = 'Off', parent: QWidget = None, indicatorPos=IndicatorPosition.LEFT):
"""
Parameters
----------
text: str
the text of switch button
parent: QWidget
parent widget
indicatorPosition: IndicatorPosition
the position of indicator
"""
self.__init__(parent, indicatorPos)
self._offText = text
self.setText(text)
def __initWidget(self):
""" initialize widgets """
self.setAttribute(Qt.WA_StyledBackground)
self.setFixedHeight(37)
self.installEventFilter(self)
# set layout
self.hBox.setSpacing(self.__spacing)
self.hBox.setContentsMargins(2, 0, 0, 0)
if self.indicatorPos == IndicatorPosition.LEFT:
self.hBox.addWidget(self.indicator)
self.hBox.addWidget(self.label)
self.hBox.setAlignment(Qt.AlignLeft)
else:
self.hBox.addWidget(self.label, 0, Qt.AlignRight)
self.hBox.addWidget(self.indicator, 0, Qt.AlignRight)
self.hBox.setAlignment(Qt.AlignRight)
# set default style sheet
FluentStyleSheet.SWITCH_BUTTON.apply(self)
# connect signal to slot
self.indicator.toggled.connect(self._updateText)
self.indicator.toggled.connect(self.checkedChanged)
def eventFilter(self, obj, e: QEvent):
if obj is self:
if e.type() == QEvent.MouseButtonPress:
self.indicator.setDown(True)
elif e.type() == QEvent.MouseButtonRelease:
self.indicator.setDown(False)
self.indicator.toggle()
elif e.type() == QEvent.Enter:
self.indicator.setAttribute(Qt.WA_UnderMouse, True)
e = QHoverEvent(QEvent.HoverEnter, QPoint(), QPoint(1, 1))
QApplication.sendEvent(self.indicator, e)
elif e.type() == QEvent.Leave:
self.indicator.setAttribute(Qt.WA_UnderMouse, False)
e = QHoverEvent(QEvent.HoverLeave, QPoint(1, 1), QPoint())
QApplication.sendEvent(self.indicator, e)
return super().eventFilter(obj, e)
def isChecked(self):
return self.indicator.isChecked()
def setChecked(self, isChecked):
""" set checked state """
self._updateText()
self.indicator.setChecked(isChecked)
def toggleChecked(self):
""" toggle checked state """
self.indicator.setChecked(not self.indicator.isChecked())
def _updateText(self):
self.setText(self.onText if self.isChecked() else self.offText)
self.adjustSize()
def getText(self):
return self._text
def setText(self, text):
self._text = text
self.label.setText(text)
self.adjustSize()
def getSpacing(self):
return self.__spacing
def setSpacing(self, spacing):
self.__spacing = spacing
self.hBox.setSpacing(spacing)
self.update()
def getOnText(self):
return self._onText
def setOnText(self, text):
self._onText = text
self._updateText()
def getOffText(self):
return self._offText
def setOffText(self, text):
self._offText = text
self._updateText()
spacing = Property(int, getSpacing, setSpacing)
checked = Property(bool, isChecked, setChecked)
text = Property(str, getText, setText)
onText = Property(str, getOnText, setOnText)
offText = Property(str, getOffText, setOffText)