File size: 8,282 Bytes
29d9465 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
"""
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()
|