|
| 1 | +#!/usr/bin/env python |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | + |
| 4 | +""" |
| 5 | +Created on 2019年3月19日 |
| 6 | +@author: Irony |
| 7 | +@site: https://pyqt5.com https://github.com/892768447 |
| 8 | +@email: 892768447@qq.com |
| 9 | +@file: CircleLine |
| 10 | +@description: |
| 11 | +""" |
| 12 | + |
| 13 | +from math import floor, pi, cos, sin |
| 14 | +from random import random, randint |
| 15 | +from time import time |
| 16 | + |
| 17 | +from PyQt5.QtCore import QTimer, Qt |
| 18 | +from PyQt5.QtGui import QColor, QPainter, QPainterPath, QPen |
| 19 | +from PyQt5.QtWidgets import QWidget |
| 20 | + |
| 21 | + |
| 22 | +__Author__ = 'Irony' |
| 23 | +__Copyright__ = 'Copyright (c) 2019' |
| 24 | + |
| 25 | +# 最小和最大半径、半径阈值和填充圆的百分比 |
| 26 | +radMin = 10 |
| 27 | +radMax = 80 |
| 28 | +filledCircle = 30 # 填充圆的百分比 |
| 29 | +concentricCircle = 60 # 同心圆百分比 |
| 30 | +radThreshold = 25 # IFF special, over this radius concentric, otherwise filled |
| 31 | +# 最小和最大移动速度 |
| 32 | +speedMin = 0.3 |
| 33 | +speedMax = 0.6 |
| 34 | +# 每个圆和模糊效果的最大透明度 |
| 35 | +maxOpacity = 0.6 |
| 36 | + |
| 37 | +colors = [ |
| 38 | + QColor(52, 168, 83), |
| 39 | + QColor(117, 95, 147), |
| 40 | + QColor(199, 108, 23), |
| 41 | + QColor(194, 62, 55), |
| 42 | + QColor(0, 172, 212), |
| 43 | + QColor(120, 120, 120) |
| 44 | +] |
| 45 | +circleBorder = 10 |
| 46 | +backgroundLine = colors[0] |
| 47 | +backgroundColor = QColor(38, 43, 46) |
| 48 | +backgroundMlt = 0.85 |
| 49 | + |
| 50 | +lineBorder = 2.5 |
| 51 | + |
| 52 | +# 最重要的是:包含它们的整个圆和数组的数目 |
| 53 | +maxCircles = 8 |
| 54 | +points = [] |
| 55 | + |
| 56 | +# 实验变量 |
| 57 | +circleExp = 1 |
| 58 | +circleExpMax = 1.003 |
| 59 | +circleExpMin = 0.997 |
| 60 | +circleExpSp = 0.00004 |
| 61 | +circlePulse = False |
| 62 | + |
| 63 | +# 生成随机整数 a<=x<=b |
| 64 | + |
| 65 | + |
| 66 | +def randint(a, b): |
| 67 | + return floor(random() * (b - a + 1) + a) |
| 68 | + |
| 69 | +# 生成随机小数 |
| 70 | + |
| 71 | + |
| 72 | +def randRange(a, b): |
| 73 | + return random() * (b - a) + a |
| 74 | + |
| 75 | +# 生成接近a的随机小数 |
| 76 | + |
| 77 | + |
| 78 | +def hyperRange(a, b): |
| 79 | + return random() * random() * random() * (b - a) + a |
| 80 | + |
| 81 | + |
| 82 | +class Circle: |
| 83 | + |
| 84 | + def __init__(self, background, width, height): |
| 85 | + self.background = background |
| 86 | + self.x = randRange(-width / 2, width / 2) |
| 87 | + self.y = randRange(-height / 2, height / 2) |
| 88 | + self.radius = hyperRange(radMin, radMax) |
| 89 | + self.filled = (False if randint( |
| 90 | + 0, 100) > concentricCircle else 'full') if self.radius < radThreshold else ( |
| 91 | + False if randint(0, 100) > concentricCircle else 'concentric') |
| 92 | + self.color = colors[randint(0, len(colors) - 1)] |
| 93 | + self.borderColor = colors[randint(0, len(colors) - 1)] |
| 94 | + self.opacity = 0.05 |
| 95 | + self.speed = randRange(speedMin, speedMax) # * (radMin / self.radius) |
| 96 | + self.speedAngle = random() * 2 * pi |
| 97 | + self.speedx = cos(self.speedAngle) * self.speed |
| 98 | + self.speedy = sin(self.speedAngle) * self.speed |
| 99 | + spacex = abs((self.x - (-1 if self.speedx < 0 else 1) * |
| 100 | + (width / 2 + self.radius)) / self.speedx) |
| 101 | + spacey = abs((self.y - (-1 if self.speedy < 0 else 1) * |
| 102 | + (height / 2 + self.radius)) / self.speedy) |
| 103 | + self.ttl = min(spacex, spacey) |
| 104 | + |
| 105 | + |
| 106 | +class CircleLineWindow(QWidget): |
| 107 | + |
| 108 | + def __init__(self, *args, **kwargs): |
| 109 | + super(CircleLineWindow, self).__init__(*args, **kwargs) |
| 110 | + geometry = QApplication.instance().desktop().availableGeometry() |
| 111 | + self.screenWidth = geometry.width() |
| 112 | + self.screenHeight = geometry.height() |
| 113 | + self._canDraw = True |
| 114 | + self._firstDraw = True |
| 115 | + self._timer = QTimer(self, timeout=self.update) |
| 116 | + |
| 117 | + def init(self): |
| 118 | + points.clear() |
| 119 | + # 链接的最小距离 |
| 120 | + self.linkDist = min(self.screenWidth, self.screenHeight) / 2.4 |
| 121 | + # 初始化点 |
| 122 | + for _ in range(maxCircles * 3): |
| 123 | + points.append(Circle('', self.screenWidth, self.screenHeight)) |
| 124 | + self.update() |
| 125 | + |
| 126 | + def resizeEvent(self, event): |
| 127 | + super(CircleLineWindow, self).resizeEvent(event) |
| 128 | + self.init() |
| 129 | + |
| 130 | + def showEvent(self, event): |
| 131 | + super(CircleLineWindow, self).showEvent(event) |
| 132 | + self._canDraw = True |
| 133 | + |
| 134 | + def hideEvent(self, event): |
| 135 | + super(CircleLineWindow, self).hideEvent(event) |
| 136 | + # 窗口最小化要停止绘制, 减少cpu占用 |
| 137 | + self._canDraw = False |
| 138 | + |
| 139 | + def paintEvent(self, event): |
| 140 | + super(CircleLineWindow, self).paintEvent(event) |
| 141 | + if not self._canDraw: |
| 142 | + return |
| 143 | + painter = QPainter(self) |
| 144 | + painter.setRenderHint(QPainter.Antialiasing) |
| 145 | + painter.setRenderHint(QPainter.SmoothPixmapTransform) |
| 146 | + painter.save() |
| 147 | + painter.fillRect(self.rect(), backgroundColor) |
| 148 | + painter.restore() |
| 149 | + self.draw(painter) |
| 150 | + |
| 151 | + def draw(self, painter): |
| 152 | + if circlePulse: |
| 153 | + if circleExp < circleExpMin or circleExp > circleExpMax: |
| 154 | + circleExpSp *= -1 |
| 155 | + circleExp += circleExpSp |
| 156 | + |
| 157 | + painter.translate(self.screenWidth / 2, self.screenHeight / 2) |
| 158 | + |
| 159 | + if self._firstDraw: |
| 160 | + t = time() |
| 161 | + self.renderPoints(painter, points) |
| 162 | + if self._firstDraw: |
| 163 | + self._firstDraw = False |
| 164 | + # 此处有个比例关系用于设置timer的时间,如果初始窗口很小,没有比例会导致动画很快 |
| 165 | + t = (time() - t) * 1000 |
| 166 | + # 比例最大不能超过1920/800 |
| 167 | + t = int(min(2.4, self.screenHeight / self.height()) * t) - 1 |
| 168 | + print('start timer(%d msec)' % t) |
| 169 | + # 开启定时器 |
| 170 | + self._timer.start(t) |
| 171 | + |
| 172 | + def drawCircle(self, painter, circle): |
| 173 | + # circle.radius *= circleExp |
| 174 | + if circle.background: |
| 175 | + circle.radius *= circleExp |
| 176 | + else: |
| 177 | + circle.radius /= circleExp |
| 178 | + radius = circle.radius |
| 179 | + |
| 180 | + r = radius * circleExp |
| 181 | + # 边框颜色设置透明度 |
| 182 | + c = QColor(circle.borderColor) |
| 183 | + c.setAlphaF(circle.opacity) |
| 184 | + |
| 185 | + painter.save() |
| 186 | + if circle.filled == 'full': |
| 187 | + # 设置背景刷 |
| 188 | + painter.setBrush(c) |
| 189 | + painter.setPen(Qt.NoPen) |
| 190 | + else: |
| 191 | + # 设置画笔 |
| 192 | + painter.setPen( |
| 193 | + QPen(c, max(1, circleBorder * (radMin - circle.radius) / (radMin - radMax)))) |
| 194 | + |
| 195 | + # 画实心圆或者圆圈 |
| 196 | + painter.drawEllipse(circle.x - r, circle.y - r, 2 * r, 2 * r) |
| 197 | + painter.restore() |
| 198 | + |
| 199 | + if circle.filled == 'concentric': |
| 200 | + r = radius / 2 |
| 201 | + # 画圆圈 |
| 202 | + painter.save() |
| 203 | + painter.setBrush(Qt.NoBrush) |
| 204 | + painter.setPen( |
| 205 | + QPen(c, max(1, circleBorder * (radMin - circle.radius) / (radMin - radMax)))) |
| 206 | + painter.drawEllipse(circle.x - r, circle.y - r, 2 * r, 2 * r) |
| 207 | + painter.restore() |
| 208 | + |
| 209 | + circle.x += circle.speedx |
| 210 | + circle.y += circle.speedy |
| 211 | + if (circle.opacity < maxOpacity): |
| 212 | + circle.opacity += 0.01 |
| 213 | + circle.ttl -= 1 |
| 214 | + |
| 215 | + def renderPoints(self, painter, circles): |
| 216 | + for i, circle in enumerate(circles): |
| 217 | + if circle.ttl < -20: |
| 218 | + # 重新初始化一个 |
| 219 | + circle = Circle('', self.screenWidth, self.screenHeight) |
| 220 | + circles[i] = circle |
| 221 | + self.drawCircle(painter, circle) |
| 222 | + |
| 223 | + circles_len = len(circles) |
| 224 | + for i in range(circles_len - 1): |
| 225 | + for j in range(i + 1, circles_len): |
| 226 | + deltax = circles[i].x - circles[j].x |
| 227 | + deltay = circles[i].y - circles[j].y |
| 228 | + dist = pow(pow(deltax, 2) + pow(deltay, 2), 0.5) |
| 229 | + # if the circles are overlapping, no laser connecting them |
| 230 | + if dist <= circles[i].radius + circles[j].radius: |
| 231 | + continue |
| 232 | + # otherwise we connect them only if the dist is < linkDist |
| 233 | + if dist < self.linkDist: |
| 234 | + xi = (1 if circles[i].x < circles[j].x else - |
| 235 | + 1) * abs(circles[i].radius * deltax / dist) |
| 236 | + yi = (1 if circles[i].y < circles[j].y else - |
| 237 | + 1) * abs(circles[i].radius * deltay / dist) |
| 238 | + xj = (-1 if circles[i].x < circles[j].x else 1) * \ |
| 239 | + abs(circles[j].radius * deltax / dist) |
| 240 | + yj = (-1 if circles[i].y < circles[j].y else 1) * \ |
| 241 | + abs(circles[j].radius * deltay / dist) |
| 242 | + path = QPainterPath() |
| 243 | + path.moveTo(circles[i].x + xi, circles[i].y + yi) |
| 244 | + path.lineTo(circles[j].x + xj, circles[j].y + yj) |
| 245 | +# samecolor = circles[i].color == circles[j].color |
| 246 | + c = QColor(circles[i].borderColor) |
| 247 | + c.setAlphaF(min(circles[i].opacity, circles[j].opacity) |
| 248 | + * ((self.linkDist - dist) / self.linkDist)) |
| 249 | + painter.setPen(QPen(c, ( |
| 250 | + lineBorder * backgroundMlt if circles[i].background else lineBorder) * ( |
| 251 | + (self.linkDist - dist) / self.linkDist))) |
| 252 | + painter.drawPath(path) |
| 253 | + |
| 254 | + |
| 255 | +if __name__ == '__main__': |
| 256 | + import sys |
| 257 | + from PyQt5.QtWidgets import QApplication |
| 258 | + app = QApplication(sys.argv) |
| 259 | + w = CircleLineWindow() |
| 260 | + w.resize(800, 600) |
| 261 | + w.show() |
| 262 | + sys.exit(app.exec_()) |
0 commit comments