|
| 1 | +#!/usr/bin/env python |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | +""" |
| 4 | +Created on 2019年1月2日 |
| 5 | +@author: Irony |
| 6 | +@site: https://pyqt.site https://github.com/PyQt5 |
| 7 | + |
| 8 | +@file: Widgets.RotateButton |
| 9 | +@description: |
| 10 | +""" |
| 11 | + |
| 12 | +import os |
| 13 | +import sys |
| 14 | + |
| 15 | +try: |
| 16 | + from PyQt5.QtCore import QPointF, QPropertyAnimation, QRectF, Qt, pyqtProperty |
| 17 | + from PyQt5.QtGui import QColor, QImage, QPainter, QPainterPath, QPixmap |
| 18 | + from PyQt5.QtWidgets import ( |
| 19 | + QGraphicsDropShadowEffect, |
| 20 | + QPushButton, |
| 21 | + QStyle, |
| 22 | + QStyleOptionButton, |
| 23 | + QStylePainter, |
| 24 | + QApplication, |
| 25 | + QWidget, |
| 26 | + QHBoxLayout, |
| 27 | + ) |
| 28 | +except ImportError: |
| 29 | + from PySide2.QtCore import QPointF, QPropertyAnimation, QRectF, Qt, pyqtProperty |
| 30 | + from PySide2.QtGui import QColor, QImage, QPainter, QPainterPath, QPixmap |
| 31 | + from PySide2.QtWidgets import ( |
| 32 | + QGraphicsDropShadowEffect, |
| 33 | + QPushButton, |
| 34 | + QStyle, |
| 35 | + QStyleOptionButton, |
| 36 | + QStylePainter, |
| 37 | + QApplication, |
| 38 | + QWidget, |
| 39 | + QHBoxLayout, |
| 40 | + ) |
| 41 | + |
| 42 | + |
| 43 | +class RotateButton(QPushButton): |
| 44 | + |
| 45 | + STARTVALUE = 0 # 起始旋转角度 |
| 46 | + ENDVALUE = 360 # 结束旋转角度 |
| 47 | + DURATION = 540 # 动画完成总时间 |
| 48 | + |
| 49 | + def __init__(self, *args, **kwargs): |
| 50 | + super(RotateButton, self).__init__(*args, **kwargs) |
| 51 | + self.setCursor(Qt.PointingHandCursor) |
| 52 | + self._angle = 0 # 角度 |
| 53 | + self._padding = 10 # 阴影边距 |
| 54 | + self._image = "" # 图片路径 |
| 55 | + self._shadowColor = QColor(33, 33, 33) # 阴影颜色 |
| 56 | + self._pixmap = None # 图片对象 |
| 57 | + # 属性动画 |
| 58 | + self._animation = QPropertyAnimation(self, b"angle", self) |
| 59 | + self._animation.setLoopCount(1) # 只循环一次 |
| 60 | + |
| 61 | + def paintEvent(self, event): |
| 62 | + """绘制事件""" |
| 63 | + text = self.text() |
| 64 | + option = QStyleOptionButton() |
| 65 | + self.initStyleOption(option) |
| 66 | + option.text = "" # 不绘制文字 |
| 67 | + painter = QStylePainter(self) |
| 68 | + painter.setRenderHint(QStylePainter.Antialiasing) |
| 69 | + painter.setRenderHint(QStylePainter.HighQualityAntialiasing) |
| 70 | + painter.setRenderHint(QStylePainter.SmoothPixmapTransform) |
| 71 | + painter.drawControl(QStyle.CE_PushButton, option) |
| 72 | + # 变换坐标为正中间 |
| 73 | + painter.translate(self.rect().center()) |
| 74 | + painter.rotate(self._angle) # 旋转 |
| 75 | + |
| 76 | + # 绘制图片 |
| 77 | + if self._pixmap and not self._pixmap.isNull(): |
| 78 | + w = self.width() |
| 79 | + h = self.height() |
| 80 | + pos = QPointF(-self._pixmap.width() / 2, -self._pixmap.height() / 2) |
| 81 | + painter.drawPixmap(pos, self._pixmap) |
| 82 | + elif text: |
| 83 | + # 在变换坐标后的正中间画文字 |
| 84 | + fm = self.fontMetrics() |
| 85 | + w = fm.width(text) |
| 86 | + h = fm.height() |
| 87 | + rect = QRectF(0 - w * 2, 0 - h, w * 2 * 2, h * 2) |
| 88 | + painter.drawText(rect, Qt.AlignCenter, text) |
| 89 | + else: |
| 90 | + super(RotateButton, self).paintEvent(event) |
| 91 | + |
| 92 | + def enterEvent(self, _): |
| 93 | + """鼠标进入事件""" |
| 94 | + # 设置阴影 |
| 95 | + # 边框阴影效果 |
| 96 | + effect = QGraphicsDropShadowEffect(self) |
| 97 | + effect.setBlurRadius(self._padding * 2) |
| 98 | + effect.setOffset(0, 0) |
| 99 | + effect.setColor(self._shadowColor) |
| 100 | + self.setGraphicsEffect(effect) |
| 101 | + |
| 102 | + # 开启旋转动画 |
| 103 | + self._animation.stop() |
| 104 | + cv = self._animation.currentValue() or self.STARTVALUE |
| 105 | + self._animation.setDuration( |
| 106 | + self.DURATION if cv == 0 else int(cv / self.ENDVALUE * self.DURATION) |
| 107 | + ) |
| 108 | + self._animation.setStartValue(cv) |
| 109 | + self._animation.setEndValue(self.ENDVALUE) |
| 110 | + self._animation.start() |
| 111 | + |
| 112 | + def leaveEvent(self, _): |
| 113 | + """鼠标离开事件""" |
| 114 | + # 取消阴影 |
| 115 | + self.setGraphicsEffect(None) |
| 116 | + |
| 117 | + # 旋转动画 |
| 118 | + self._animation.stop() |
| 119 | + cv = self._animation.currentValue() or self.ENDVALUE |
| 120 | + self._animation.setDuration(int(cv / self.ENDVALUE * self.DURATION)) |
| 121 | + self._animation.setStartValue(cv) |
| 122 | + self._animation.setEndValue(self.STARTVALUE) |
| 123 | + self._animation.start() |
| 124 | + |
| 125 | + def setPixmap(self, path): |
| 126 | + if not os.path.exists(path): |
| 127 | + self._image = "" |
| 128 | + self._pixmap = None |
| 129 | + return |
| 130 | + self._image = path |
| 131 | + size = ( |
| 132 | + max( |
| 133 | + min(self.width(), self.height()), |
| 134 | + min(self.minimumWidth(), self.minimumHeight()), |
| 135 | + ) |
| 136 | + - self._padding |
| 137 | + ) # 需要边距的边框 |
| 138 | + radius = int(size / 2) |
| 139 | + image = QImage(size, size, QImage.Format_ARGB32_Premultiplied) |
| 140 | + image.fill(Qt.transparent) # 填充背景为透明 |
| 141 | + pixmap = QPixmap(path).scaled( |
| 142 | + size, size, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation |
| 143 | + ) |
| 144 | + # QPainter |
| 145 | + painter = QPainter() |
| 146 | + painter.begin(image) |
| 147 | + painter.setRenderHint(QPainter.Antialiasing, True) |
| 148 | + painter.setRenderHint(QPainter.SmoothPixmapTransform, True) |
| 149 | + # QPainterPath |
| 150 | + path = QPainterPath() |
| 151 | + path.addRoundedRect(0, 0, size, size, radius, radius) |
| 152 | + # 切割圆 |
| 153 | + painter.setClipPath(path) |
| 154 | + painter.drawPixmap(0, 0, pixmap) |
| 155 | + painter.end() |
| 156 | + self._pixmap = QPixmap.fromImage(image) |
| 157 | + self.update() |
| 158 | + |
| 159 | + def pixmap(self): |
| 160 | + return self._pixmap |
| 161 | + |
| 162 | + @pyqtProperty(str) |
| 163 | + def image(self): |
| 164 | + return self._image |
| 165 | + |
| 166 | + @image.setter |
| 167 | + def image(self, path): |
| 168 | + self.setPixmap(path) |
| 169 | + |
| 170 | + @pyqtProperty(int) |
| 171 | + def angle(self): |
| 172 | + return self._angle |
| 173 | + |
| 174 | + @angle.setter |
| 175 | + def angle(self, value): |
| 176 | + self._angle = value |
| 177 | + self.update() |
| 178 | + |
| 179 | + @pyqtProperty(int) |
| 180 | + def padding(self): |
| 181 | + return self._padding |
| 182 | + |
| 183 | + @padding.setter |
| 184 | + def padding(self, value): |
| 185 | + self._padding = value |
| 186 | + |
| 187 | + @pyqtProperty(QColor) |
| 188 | + def shadowColor(self): |
| 189 | + return self._shadowColor |
| 190 | + |
| 191 | + @shadowColor.setter |
| 192 | + def shadowColor(self, color): |
| 193 | + self._shadowColor = QColor(color) |
| 194 | + |
| 195 | + |
| 196 | +class TestWindow(QWidget): |
| 197 | + |
| 198 | + def __init__(self, *args, **kwargs): |
| 199 | + super().__init__(*args, **kwargs) |
| 200 | + layout = QHBoxLayout(self) |
| 201 | + |
| 202 | + btn = RotateButton("pyqt.site", self) |
| 203 | + btn.setMinimumHeight(96) |
| 204 | + btn.setToolTip("旋转按钮") |
| 205 | + layout.addWidget(btn) |
| 206 | + |
| 207 | + btn = RotateButton("", self) |
| 208 | + btn.setMinimumHeight(96) |
| 209 | + btn.setObjectName("imageLabel1") |
| 210 | + btn.setPixmap("./Data/Images/avatar.jpg") |
| 211 | + layout.addWidget(btn) |
| 212 | + |
| 213 | + btn = RotateButton("", self) |
| 214 | + btn.setMinimumHeight(96) |
| 215 | + btn.setObjectName("imageLabel2") |
| 216 | + layout.addWidget(btn) |
| 217 | + |
| 218 | + |
| 219 | +if __name__ == "__main__": |
| 220 | + import cgitb |
| 221 | + |
| 222 | + cgitb.enable(1, None, 5, "text") |
| 223 | + |
| 224 | + # cd to current dir |
| 225 | + os.chdir(os.path.dirname(os.path.abspath(sys.argv[0]))) |
| 226 | + |
| 227 | + app = QApplication(sys.argv) |
| 228 | + app.setStyleSheet( |
| 229 | + """ |
| 230 | + RotateButton { |
| 231 | + font-size: 48px; |
| 232 | + } |
| 233 | + #imageLabel1, #imageLabel2 { |
| 234 | + background: transparent; |
| 235 | + } |
| 236 | + #imageLabel2 { |
| 237 | + qproperty-image: "./Data/Images/avatar.jpg"; |
| 238 | + } |
| 239 | + """ |
| 240 | + ) |
| 241 | + w = TestWindow() |
| 242 | + w.show() |
| 243 | + sys.exit(app.exec_()) |
0 commit comments