""" Author: Luan Dias - https://github.com/luandiasrj Url: https://github.com/luandiasrj/QToggle_-_Advanced_QCheckbox_for_PyQT6 This code implements a custom QToggle class, which is a toggle switch derived from QCheckBox. The QToggle class features customizable colors, and properties. It includes smooth transitions when toggling between states. The custom properties include: bg_color: background color of the toggle circle_color: color of the circle inside the toggle active_color: color of the background when the toggle is checked disabled_color: color of the toggle when it's disabled text_color: color of the text Main functions and methods in the class include: init: Initializes the QToggle object with default colors and settings setDuration: Set the duration for the animation update_pos_color: Updates the circle position and background color start_transition: Starts the transition animation when the state changes create_animation: Creates the circle position animation create_bg_color_animation: Creates the background color animation sizeHint: Provides the recommended size for the toggle hitButton: Determines if the mouse click is inside the toggle area paintEvent: Handles the custom painting of the toggle The example demonstrates how to use the QToggle class by creating three different toggles with various settings such as custom height, colors, and font. """ from PyQt6.QtCore import Qt, QRect, pyqtProperty, QPropertyAnimation, QPoint, \ QEasingCurve from PyQt6.QtGui import QColor, QFontMetrics, QPainter, QPainterPath, QBrush, \ QPen, QFont from PyQt6.QtWidgets import QApplication, QWidget, QCheckBox, QVBoxLayout class QToggle(QCheckBox): bg_color = pyqtProperty( QColor, lambda self: self._bg_color, lambda self, col: setattr(self, '_bg_color', col)) circle_color = pyqtProperty( QColor, lambda self: self._circle_color, lambda self, col: setattr(self, '_circle_color', col)) active_color = pyqtProperty( QColor, lambda self: self._active_color, lambda self, col: setattr(self, '_active_color', col)) disabled_color = pyqtProperty( QColor, lambda self: self._disabled_color, lambda self, col: setattr(self, '_disabled_color', col)) text_color = pyqtProperty( QColor, lambda self: self._text_color, lambda self, col: setattr(self, '_text_color', col)) def __init__(self, parent=None): super().__init__(parent) self._bg_color, self._circle_color, self._active_color, \ self._disabled_color, self._text_color = QColor("#0BF"), \ QColor("#DDD"), QColor('#777'), QColor("#CCC"), QColor("#000") self._circle_pos, self._intermediate_bg_color = None, None self.setFixedHeight(18) self._animation_duration = 500 # milliseconds self.stateChanged.connect(self.start_transition) self._user_checked = False # Introduced flag to check user-initiated changes circle_pos = pyqtProperty( float, lambda self: self._circle_pos, lambda self, pos: (setattr(self, '_circle_pos', pos), self.update())) intermediate_bg_color = pyqtProperty( QColor, lambda self: self._intermediate_bg_color, lambda self, col: setattr(self, '_intermediate_bg_color', col)) def setDuration(self, duration: int): """ Set the duration for the animation. :param duration: Duration in milliseconds. """ self._animation_duration = duration def update_pos_color(self, checked=None): self._circle_pos = self.height() * (1.1 if checked else 0.1) if self.isChecked(): self._intermediate_bg_color = self._active_color else: self._intermediate_bg_color = self._bg_color def start_transition(self, state): if not self._user_checked: # Skip animation if change isn't user-initiated self.update_pos_color(state) return for anim in [self.create_animation, self.create_bg_color_animation]: animation = anim(state) animation.start() self._user_checked = False # Reset the flag after animation starts def mousePressEvent(self, event): self._user_checked = True # Set flag when user manually clicks the toggle super().mousePressEvent(event) def create_animation(self, state): return self._create_common_animation( state, b'circle_pos', self.height() * 0.1, self.height() * 1.1) def create_bg_color_animation(self, state): return self._create_common_animation( state, b'intermediate_bg_color', self._bg_color, self._active_color) def _create_common_animation(self, state, prop, start_val, end_val): animation = QPropertyAnimation(self, prop, self) animation.setEasingCurve(QEasingCurve.Type.InOutCubic) animation.setDuration(self._animation_duration) animation.setStartValue(start_val if state else end_val) animation.setEndValue(end_val if state else start_val) return animation def showEvent(self, event): super().showEvent(event) # Ensure to call the super class's implementation self.update_pos_color(self.isChecked()) def resizeEvent(self, event): self.update_pos_color(self.isChecked()) def sizeHint(self): size = super().sizeHint() text_width = QFontMetrics( self.font()).boundingRect(self.text()).width() size.setWidth(int(self.height() * 2 + text_width * 1.075)) return size def hitButton(self, pos: QPoint): return self.contentsRect().contains(pos) def paintEvent(self, event): painter = QPainter(self) painter.setRenderHint(QPainter.RenderHint.Antialiasing) circle_color = QColor( self.disabled_color if not self.isEnabled() else self.circle_color) bg_color = QColor( self.disabled_color if not self.isEnabled() else self.intermediate_bg_color) text_color = QColor( self.disabled_color if not self.isEnabled() else self.text_color) bordersradius = self.height() / 2 togglewidth = self.height() * 2 togglemargin = self.height() * 0.3 circlesize = self.height() * 0.8 bg_path = QPainterPath() bg_path.addRoundedRect( 0, 0, togglewidth, self.height(), bordersradius, bordersradius) painter.fillPath(bg_path, QBrush(bg_color)) circle = QPainterPath() circle.addEllipse( self.circle_pos, self.height() * 0.1, circlesize, circlesize) painter.fillPath(circle, QBrush(circle_color)) painter.setPen(QPen(QColor(text_color))) painter.setFont(self.font()) text_rect = QRect(int(togglewidth + togglemargin), 0, self.width() - int(togglewidth + togglemargin), self.height()) text_rect.adjust( 0, (self.height() - painter.fontMetrics().height()) // 2, 0, 0) painter.drawText(text_rect, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, self.text()) painter.end() if __name__ == '__main__': app = QApplication([]) window = QWidget() layout = QVBoxLayout() checkbox0 = QToggle() checkbox0.setFixedHeight(12) layout.addWidget(checkbox0) checkbox1 = QToggle() checkbox1.setText('Checkbox 1 - Disabled') checkbox1.setEnabled(False) layout.addWidget(checkbox1) checkbox2 = QToggle() checkbox2.setText('Checkbox 2 - Checked, custom height, animation duration, colors and font') checkbox2.setFixedHeight(24) checkbox2.setFont(QFont('Segoe Print', 10)) checkbox2.setStyleSheet("QToggle{" "qproperty-bg_color:#FAA;" "qproperty-circle_color:#DDF;" "qproperty-active_color:#AAF;" "qproperty-disabled_color:#777;" "qproperty-text_color:#A0F;}") checkbox2.setDuration(2000) checkbox2.setChecked(True) layout.addWidget(checkbox2) window.setLayout(layout) window.show() app.exec()