1#!/usr/bin/env python 2 3""" 4bubbleswidget.py 5 6A PyQt custom widget example for Qt Designer. 7 8Copyright (C) 2006 David Boddie <david@boddie.org.uk> 9Copyright (C) 2005-2006 Trolltech ASA. All rights reserved. 10 11This program is free software; you can redistribute it and/or modify 12it under the terms of the GNU General Public License as published by 13the Free Software Foundation; either version 2 of the License, or 14(at your option) any later version. 15 16This program is distributed in the hope that it will be useful, 17but WITHOUT ANY WARRANTY; without even the implied warranty of 18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19GNU General Public License for more details. 20 21You should have received a copy of the GNU General Public License 22along with this program; if not, write to the Free Software 23Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 24""" 25 26import random 27 28from PyQt5.QtCore import (pyqtProperty, pyqtSignal, pyqtSlot, QPointF, QRectF, 29 QSize, QSizeF, Qt, QTimer) 30from PyQt5.QtGui import QBrush, QColor, QPainter, QPen, QRadialGradient 31from PyQt5.QtWidgets import QApplication, QWidget 32 33 34class BaseClass(QWidget): 35 """BaseClass(QWidget) 36 37 Provides a base custom widget class to show that properties implemented 38 in Python can be inherited and shown as belonging to distinct classes 39 in Qt Designer's Property Editor. 40 """ 41 42 def __init__(self, parent=None): 43 44 super(BaseClass, self).__init__(parent) 45 46 self.resetAuthor() 47 48 # Define getter, setter and resetter methods for the author property. 49 50 def getAuthor(self): 51 return self._author 52 53 def setAuthor(self, name): 54 self._author = name 55 56 def resetAuthor(self): 57 self._author = "David Boddie" 58 59 author = pyqtProperty(str, getAuthor, setAuthor, resetAuthor) 60 61 62class Bubble: 63 """Bubble 64 65 Provides a class to represent individual bubbles in a BubblesWidget. 66 Each Bubble instance can render itself onto a paint device using a 67 QPainter passed to its drawBubble() method. 68 """ 69 70 def __init__(self, position, radius, speed, innerColor, outerColor): 71 72 self.position = position 73 self.radius = radius 74 self.speed = speed 75 self.innerColor = innerColor 76 self.outerColor = outerColor 77 self.updateBrush() 78 79 def updateBrush(self): 80 81 gradient = QRadialGradient( 82 QPointF(self.radius, self.radius), self.radius, 83 QPointF(self.radius*0.5, self.radius*0.5)) 84 85 gradient.setColorAt(0, QColor(255, 255, 255, 255)) 86 gradient.setColorAt(0.25, self.innerColor) 87 gradient.setColorAt(1, self.outerColor) 88 self.brush = QBrush(gradient) 89 90 def drawBubble(self, painter): 91 92 painter.save() 93 painter.translate(self.position.x() - self.radius, 94 self.position.y() - self.radius) 95 painter.setBrush(self.brush) 96 painter.drawEllipse(0.0, 0.0, 2*self.radius, 2*self.radius) 97 painter.restore() 98 99 100class BubblesWidget(BaseClass): 101 """BubblesWidget(BaseClass) 102 103 Provides a custom widget that shows a number of rising bubbles. 104 Various properties are defined so that the user can customize the 105 appearance of the widget, and change the number and behaviour of the 106 bubbles shown. 107 """ 108 109 # We define two signals that are used to indicate changes to the status 110 # of the widget. 111 bubbleLeft = pyqtSignal() 112 bubblesRemaining = pyqtSignal(int) 113 114 def __init__(self, parent=None): 115 116 super(BubblesWidget, self).__init__(parent) 117 118 self.pen = QPen(QColor("#cccccc")) 119 self.bubbles = [] 120 self.backgroundColor1 = self.randomColor() 121 self.backgroundColor2 = self.randomColor().darker(150) 122 self.newBubble = None 123 124 random.seed() 125 126 self.animation_timer = QTimer(self) 127 self.animation_timer.setSingleShot(False) 128 self.animation_timer.timeout.connect(self.animate) 129 self.animation_timer.start(25) 130 131 self.bubbleTimer = QTimer() 132 self.bubbleTimer.setSingleShot(False) 133 self.bubbleTimer.timeout.connect(self.expandBubble) 134 135 self.setMouseTracking(True) 136 self.setMinimumSize(QSize(200, 200)) 137 self.setWindowTitle("Bubble Maker") 138 139 def paintEvent(self, event): 140 141 background = QRadialGradient(QPointF(self.rect().topLeft()), 500, 142 QPointF(self.rect().bottomRight())) 143 background.setColorAt(0, self.backgroundColor1) 144 background.setColorAt(1, self.backgroundColor2) 145 146 painter = QPainter() 147 painter.begin(self) 148 painter.setRenderHint(QPainter.Antialiasing) 149 painter.fillRect(event.rect(), QBrush(background)) 150 151 painter.setPen(self.pen) 152 153 for bubble in self.bubbles: 154 155 if QRectF(bubble.position - QPointF(bubble.radius, bubble.radius), 156 QSizeF(2*bubble.radius, 2*bubble.radius)).intersects(QRectF(event.rect())): 157 bubble.drawBubble(painter) 158 159 if self.newBubble: 160 161 self.newBubble.drawBubble(painter) 162 163 painter.end() 164 165 def mousePressEvent(self, event): 166 167 if event.button() == Qt.LeftButton and self.newBubble is None: 168 169 self.newBubble = Bubble(QPointF(event.pos()), 4.0, 170 1.0 + random.random() * 7, 171 self.randomColor(), self.randomColor()) 172 self.bubbleTimer.start(50) 173 event.accept() 174 175 def mouseMoveEvent(self, event): 176 177 if self.newBubble: 178 179 self.update( 180 QRectF(self.newBubble.position - \ 181 QPointF(self.newBubble.radius + 1, self.newBubble.radius + 1), 182 QSizeF(2*self.newBubble.radius + 2, 2*self.newBubble.radius + 2)).toRect() 183 ) 184 self.newBubble.position = QPointF(event.pos()) 185 self.update( 186 QRectF(self.newBubble.position - \ 187 QPointF(self.newBubble.radius + 1, self.newBubble.radius + 1), 188 QSizeF(2*self.newBubble.radius + 2, 2*self.newBubble.radius + 2)).toRect() 189 ) 190 191 event.accept() 192 193 def mouseReleaseEvent(self, event): 194 195 if self.newBubble: 196 197 self.bubbles.append(self.newBubble) 198 self.newBubble = None 199 self.bubbleTimer.stop() 200 self.bubblesRemaining.emit(len(self.bubbles)) 201 202 event.accept() 203 204 def expandBubble(self): 205 206 if self.newBubble: 207 208 self.newBubble.radius = min(self.newBubble.radius + 4.0, 209 self.width()/8.0, self.height()/8.0) 210 self.update( 211 QRectF(self.newBubble.position - \ 212 QPointF(self.newBubble.radius + 1, self.newBubble.radius + 1), 213 QSizeF(2*self.newBubble.radius + 2, 2*self.newBubble.radius + 2)).toRect() 214 ) 215 self.newBubble.updateBrush() 216 217 def randomColor(self): 218 219 red = 205 + random.random() * 50 220 green = 205 + random.random() * 50 221 blue = 205 + random.random() * 50 222 alpha = 91 + random.random() * 100 223 224 return QColor(red, green, blue, alpha) 225 226 def animate(self): 227 228 bubbles = [] 229 left = False 230 for bubble in self.bubbles: 231 232 bubble.position = bubble.position + QPointF(0, -bubble.speed) 233 234 self.update( 235 QRectF(bubble.position - QPointF(bubble.radius + 1, 236 bubble.radius + 1), 237 QSizeF(2*bubble.radius + 2, 2*bubble.radius + 2 + bubble.speed)).toRect()) 238 239 if bubble.position.y() + bubble.radius > 0: 240 bubbles.append(bubble) 241 else: 242 self.bubbleLeft.emit() 243 left = True 244 245 if self.newBubble: 246 self.update( 247 QRectF(self.newBubble.position - QPointF( 248 self.newBubble.radius + 1, 249 self.newBubble.radius + 1), 250 QSizeF(2*self.newBubble.radius + 2, 2*self.newBubble.radius + 2)).toRect()) 251 252 self.bubbles = bubbles 253 if left: 254 self.bubblesRemaining.emit(len(self.bubbles)) 255 256 def sizeHint(self): 257 258 return QSize(200, 200) 259 260 # We provide getter and setter methods for the numberOfBubbles property. 261 def getBubbles(self): 262 263 return len(self.bubbles) 264 265 # The setBubbles() method can also be used as a slot. 266 @pyqtSlot(int) 267 def setBubbles(self, value): 268 269 value = max(0, value) 270 271 while len(self.bubbles) < value: 272 273 newBubble = Bubble(QPointF(random.random() * self.width(), 274 random.random() * self.height()), 275 4.0 + random.random() * 20, 276 1.0 + random.random() * 7, 277 self.randomColor(), self.randomColor()) 278 newBubble.updateBrush() 279 self.bubbles.append(newBubble) 280 281 self.bubbles = self.bubbles[:value] 282 self.bubblesRemaining.emit(value) 283 self.update() 284 285 numberOfBubbles = pyqtProperty(int, getBubbles, setBubbles) 286 287 # We provide getter and setter methods for the color1 and color2 288 # properties. The red, green and blue components for the QColor 289 # values stored in these properties can be edited individually in 290 # Qt Designer. 291 292 def getColor1(self): 293 294 return self.backgroundColor1 295 296 def setColor1(self, value): 297 298 self.backgroundColor1 = QColor(value) 299 self.update() 300 301 color1 = pyqtProperty(QColor, getColor1, setColor1) 302 303 def getColor2(self): 304 305 return self.backgroundColor2 306 307 def setColor2(self, value): 308 309 self.backgroundColor2 = QColor(value) 310 self.update() 311 312 color2 = pyqtProperty(QColor, getColor2, setColor2) 313 314 # The stop() and start() slots provide simple control over the animation 315 # of the bubbles in the widget. 316 317 @pyqtSlot() 318 def stop(self): 319 320 self.animation_timer.stop() 321 322 @pyqtSlot() 323 def start(self): 324 325 self.animation_timer.start(25) 326 327 328if __name__ == "__main__": 329 330 import sys 331 332 app = QApplication(sys.argv) 333 widget = BubblesWidget() 334 widget.show() 335 sys.exit(app.exec_()) 336