splash_screen.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. import sys
  2. import os
  3. from PyQt5.QtWidgets import (QApplication, QSplashScreen, QProgressBar,
  4. QLabel, QVBoxLayout, QWidget, QFrame, QDesktopWidget)
  5. from PyQt5.QtCore import Qt, QTimer, QSize, QPropertyAnimation, QEasingCurve, QPoint
  6. from PyQt5.QtGui import QPixmap, QFont, QColor, QPainter, QBrush, QPen, QRadialGradient, QLinearGradient, QMovie
  7. import math # 添加数学库
  8. class ParticleEffect(QWidget):
  9. """粒子效果动画组件"""
  10. def __init__(self, parent=None):
  11. super().__init__(parent)
  12. self.setAttribute(Qt.WA_TranslucentBackground)
  13. self.setAttribute(Qt.WA_TransparentForMouseEvents)
  14. self.particles = []
  15. self.timer = QTimer(self)
  16. self.timer.timeout.connect(self.update_particles)
  17. self.timer.start(50) # 每50毫秒更新一次
  18. # 初始化粒子
  19. for _ in range(100): # 增加粒子数量
  20. self.particles.append({
  21. 'x': self.width() * 0.5,
  22. 'y': self.height() * 0.5,
  23. 'vx': (0.5 - float(os.urandom(1)[0]) / 255.0) * 8, # 增加速度范围
  24. 'vy': (0.5 - float(os.urandom(1)[0]) / 255.0) * 8,
  25. 'size': 1 + float(os.urandom(1)[0]) / 32.0, # 增加粒子大小
  26. 'alpha': 0.5 + float(os.urandom(1)[0]) / 255.0,
  27. 'color': QColor(100 + int(os.urandom(1)[0]) % 155,
  28. 200 + int(os.urandom(1)[0]) % 55,
  29. 220 + int(os.urandom(1)[0]) % 35, # 更亮的蓝色调
  30. 200)
  31. })
  32. def update_particles(self):
  33. width = self.width() or 1920 # 防止宽度为0
  34. height = self.height() or 1080
  35. for p in self.particles:
  36. # 更新位置
  37. p['x'] += p['vx']
  38. p['y'] += p['vy']
  39. # 如果超出边界,重新生成粒子
  40. if (p['x'] < 0 or p['x'] > width or
  41. p['y'] < 0 or p['y'] > height):
  42. p['x'] = width * 0.5 + (width * 0.3 * (float(os.urandom(1)[0]) / 255.0 - 0.5)) # 随机中心位置
  43. p['y'] = height * 0.5 + (height * 0.3 * (float(os.urandom(1)[0]) / 255.0 - 0.5))
  44. p['vx'] = (0.5 - float(os.urandom(1)[0]) / 255.0) * 8
  45. p['vy'] = (0.5 - float(os.urandom(1)[0]) / 255.0) * 8
  46. self.update() # 触发重绘
  47. def paintEvent(self, event):
  48. painter = QPainter(self)
  49. painter.setRenderHint(QPainter.Antialiasing)
  50. for p in self.particles:
  51. painter.setPen(Qt.NoPen)
  52. painter.setBrush(p['color'])
  53. size = p['size']
  54. painter.drawEllipse(p['x'] - size/2, p['y'] - size/2, size, size)
  55. # 为部分粒子添加轻微的发光效果
  56. if size > 2:
  57. glow = QColor(p['color'])
  58. glow.setAlpha(50)
  59. painter.setBrush(glow)
  60. painter.drawEllipse(p['x'] - size, p['y'] - size, size * 2, size * 2)
  61. class HexEffect(QWidget):
  62. """六边形网格效果"""
  63. def __init__(self, parent=None):
  64. super().__init__(parent)
  65. self.setAttribute(Qt.WA_TranslucentBackground)
  66. self.setAttribute(Qt.WA_TransparentForMouseEvents)
  67. self.hexagons = []
  68. self.timer = QTimer(self)
  69. self.timer.timeout.connect(self.update_hexagons)
  70. self.timer.start(100) # 每100毫秒更新一次
  71. self.alpha_direction = 1 # 透明度变化方向
  72. self.current_alpha = 40 # 初始透明度
  73. def update_hexagons(self):
  74. # 更新六边形透明度
  75. self.current_alpha += self.alpha_direction
  76. if self.current_alpha >= 60: # 最大透明度
  77. self.alpha_direction = -1
  78. elif self.current_alpha <= 20: # 最小透明度
  79. self.alpha_direction = 1
  80. self.update() # 触发重绘
  81. def paintEvent(self, event):
  82. painter = QPainter(self)
  83. painter.setRenderHint(QPainter.Antialiasing)
  84. hex_size = 60 # 六边形大小
  85. width = self.width()
  86. height = self.height()
  87. # 计算六边形网格
  88. horizontal_spacing = hex_size * 1.5
  89. vertical_spacing = hex_size * 0.866 * 2 # sqrt(3)/2 * 2 * hex_size
  90. rows = int(height / vertical_spacing) + 2
  91. cols = int(width / horizontal_spacing) + 2
  92. color = QColor(0, 180, 220, self.current_alpha)
  93. painter.setPen(QPen(QColor(0, 220, 255, 80), 1))
  94. for row in range(rows):
  95. for col in range(cols):
  96. x = col * horizontal_spacing
  97. y = row * vertical_spacing
  98. # 偶数行需要偏移
  99. if row % 2 == 1:
  100. x += hex_size * 0.75
  101. # 绘制六边形
  102. painter.setBrush(QBrush(color))
  103. self.draw_hexagon(painter, x, y, hex_size)
  104. def draw_hexagon(self, painter, x, y, size):
  105. """绘制六边形"""
  106. points = []
  107. for i in range(6):
  108. angle_deg = 60 * i - 30
  109. angle_rad = 3.14159 * angle_deg / 180
  110. point_x = x + size * 0.5 * math.cos(angle_rad)
  111. point_y = y + size * 0.5 * math.sin(angle_rad)
  112. points.append(QPoint(point_x, point_y))
  113. painter.drawPolygon(points)
  114. class CircuitEffect(QWidget):
  115. """电路板效果"""
  116. def __init__(self, parent=None):
  117. super().__init__(parent)
  118. self.setAttribute(Qt.WA_TranslucentBackground)
  119. self.setAttribute(Qt.WA_TransparentForMouseEvents)
  120. self.circuit_points = []
  121. self.circuit_lines = []
  122. self.pulse_positions = {} # 线路上的脉冲位置
  123. self.timer = QTimer(self)
  124. self.timer.timeout.connect(self.update_pulses)
  125. self.timer.start(50) # 每50毫秒更新一次
  126. def generate_circuits(self, width, height):
  127. """生成电路图案"""
  128. self.circuit_points = []
  129. self.circuit_lines = []
  130. self.pulse_positions = {}
  131. # 网格大小
  132. cell_size = 100
  133. cols = width // cell_size + 1
  134. rows = height // cell_size + 1
  135. # 生成网格点
  136. for row in range(rows):
  137. for col in range(cols):
  138. # 添加一些随机性
  139. jitter_x = (float(os.urandom(1)[0]) / 255.0 - 0.5) * cell_size * 0.5
  140. jitter_y = (float(os.urandom(1)[0]) / 255.0 - 0.5) * cell_size * 0.5
  141. x = col * cell_size + jitter_x
  142. y = row * cell_size + jitter_y
  143. self.circuit_points.append((x, y))
  144. # 生成线条连接
  145. for i, point1 in enumerate(self.circuit_points):
  146. # 找到最近的几个点
  147. distances = []
  148. for j, point2 in enumerate(self.circuit_points):
  149. if i != j:
  150. dx = point1[0] - point2[0]
  151. dy = point1[1] - point2[1]
  152. distance = (dx * dx + dy * dy) ** 0.5
  153. if distance < cell_size * 1.8: # 只连接较近的点
  154. distances.append((distance, j))
  155. # 最多连接3条线
  156. distances.sort()
  157. for k in range(min(3, len(distances))):
  158. j = distances[k][1]
  159. if i < j: # 避免重复添加
  160. self.circuit_lines.append((i, j))
  161. # 初始化脉冲位置,20%的线有脉冲
  162. if float(os.urandom(1)[0]) / 255.0 < 0.2:
  163. self.pulse_positions[(i, j)] = 0.0
  164. def update_pulses(self):
  165. """更新脉冲位置"""
  166. # 更新现有脉冲
  167. keys_to_remove = []
  168. for line, pos in self.pulse_positions.items():
  169. self.pulse_positions[line] = pos + 0.02 # 脉冲前进速度
  170. if self.pulse_positions[line] > 1.0:
  171. keys_to_remove.append(line)
  172. # 移除完成的脉冲
  173. for key in keys_to_remove:
  174. del self.pulse_positions[key]
  175. # 随机添加新脉冲
  176. if len(self.circuit_lines) > 0 and len(self.pulse_positions) < len(self.circuit_lines) * 0.2:
  177. if float(os.urandom(1)[0]) / 255.0 < 0.1: # 10%几率添加新脉冲
  178. line_idx = int(float(os.urandom(1)[0]) / 255.0 * len(self.circuit_lines))
  179. # 确保索引不超出范围
  180. line_idx = min(line_idx, len(self.circuit_lines) - 1)
  181. line = self.circuit_lines[line_idx]
  182. if line not in self.pulse_positions:
  183. self.pulse_positions[line] = 0.0
  184. self.update() # 触发重绘
  185. def paintEvent(self, event):
  186. # 如果还没有生成电路,则生成
  187. if not self.circuit_points:
  188. self.generate_circuits(self.width(), self.height())
  189. painter = QPainter(self)
  190. painter.setRenderHint(QPainter.Antialiasing)
  191. # 绘制线条
  192. painter.setPen(QPen(QColor(0, 180, 220, 40), 1))
  193. for i, j in self.circuit_lines:
  194. p1 = self.circuit_points[i]
  195. p2 = self.circuit_points[j]
  196. painter.drawLine(p1[0], p1[1], p2[0], p2[1])
  197. # 绘制脉冲
  198. for line, pos in self.pulse_positions.items():
  199. i, j = line
  200. p1 = self.circuit_points[i]
  201. p2 = self.circuit_points[j]
  202. # 计算脉冲位置
  203. pulse_x = p1[0] + (p2[0] - p1[0]) * pos
  204. pulse_y = p1[1] + (p2[1] - p1[1]) * pos
  205. # 绘制脉冲(亮点)
  206. gradient = QRadialGradient(pulse_x, pulse_y, 10)
  207. gradient.setColorAt(0, QColor(0, 230, 255, 200))
  208. gradient.setColorAt(1, QColor(0, 230, 255, 0))
  209. painter.setBrush(QBrush(gradient))
  210. painter.setPen(Qt.NoPen)
  211. painter.drawEllipse(pulse_x - 10, pulse_y - 10, 20, 20)
  212. # 绘制交叉点
  213. painter.setPen(Qt.NoPen)
  214. painter.setBrush(QBrush(QColor(0, 200, 220, 100)))
  215. for point in self.circuit_points:
  216. painter.drawEllipse(point[0] - 3, point[1] - 3, 6, 6)
  217. class CustomSplashScreen(QSplashScreen):
  218. """自定义启动屏幕"""
  219. def __init__(self):
  220. super().__init__()
  221. self.setWindowFlag(Qt.FramelessWindowHint)
  222. self.setWindowFlag(Qt.WindowStaysOnTopHint)
  223. # 获取屏幕尺寸并全屏显示
  224. desktop = QDesktopWidget().availableGeometry()
  225. self.screen_width = desktop.width()
  226. self.screen_height = desktop.height()
  227. self.setGeometry(0, 0, self.screen_width, self.screen_height)
  228. # 创建基础窗口
  229. self.setStyleSheet("""
  230. QSplashScreen {
  231. background-color: #0a1a2a;
  232. border: 0px;
  233. }
  234. """)
  235. # 创建中央组件来布局内容
  236. self.central_widget = QWidget(self)
  237. self.central_widget.setGeometry(0, 0, self.screen_width, self.screen_height)
  238. # 主布局
  239. self.layout = QVBoxLayout(self.central_widget)
  240. self.layout.setContentsMargins(40, 40, 40, 40)
  241. self.layout.setSpacing(15)
  242. # 添加空白占位
  243. self.layout.addStretch(1)
  244. # 添加标题
  245. self.title_label = QLabel("森林多模态灾害监测系统", self)
  246. self.title_label.setAlignment(Qt.AlignCenter)
  247. font = QFont("Microsoft YaHei", 42, QFont.Bold)
  248. self.title_label.setFont(font)
  249. self.title_label.setStyleSheet("color: #00e6e6; margin-top: 20px;")
  250. self.layout.addWidget(self.title_label)
  251. # 添加英文副标题
  252. self.subtitle_label = QLabel("Forest Multi-modal Disaster Monitoring System", self)
  253. self.subtitle_label.setAlignment(Qt.AlignCenter)
  254. subtitle_font = QFont("Arial", 20)
  255. subtitle_font.setItalic(True)
  256. self.subtitle_label.setFont(subtitle_font)
  257. self.subtitle_label.setStyleSheet("color: #99f2ff; margin-bottom: 20px;")
  258. self.layout.addWidget(self.subtitle_label)
  259. # 添加分隔线
  260. self.separator = QFrame(self)
  261. self.separator.setFrameShape(QFrame.HLine)
  262. self.separator.setStyleSheet("background-color: #00a0c0; margin: 20px 200px;")
  263. self.separator.setFixedHeight(3)
  264. self.layout.addWidget(self.separator)
  265. # 添加加载动画
  266. self.animation_label = QLabel(self)
  267. self.animation_label.setAlignment(Qt.AlignCenter)
  268. # 检查GIF文件是否存在,否则使用替代动画
  269. animation_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'ui', 'assets', 'loading.gif')
  270. if os.path.exists(animation_path):
  271. try:
  272. self.animation = QMovie(animation_path)
  273. self.animation.setScaledSize(QSize(200, 200))
  274. self.animation_label.setMovie(self.animation)
  275. self.animation.start()
  276. except Exception as e:
  277. print(f"加载动画文件失败: {e}")
  278. self.animation_label.setText("系统初始化中...")
  279. self.animation_label.setStyleSheet("color: #99f2ff; font-size: 24px;")
  280. else:
  281. # 使用文本代替动画
  282. self.animation_label.setText("系统初始化中...")
  283. self.animation_label.setStyleSheet("color: #99f2ff; font-size: 24px;")
  284. print(f"动画文件不存在: {animation_path}")
  285. self.layout.addWidget(self.animation_label)
  286. # 添加状态标签
  287. self.status_label = QLabel("正在加载组件...", self)
  288. self.status_label.setAlignment(Qt.AlignCenter)
  289. self.status_label.setStyleSheet("color: #99f2ff; font-size: 20px; margin-top: 20px;")
  290. self.layout.addWidget(self.status_label)
  291. # 添加进度条
  292. self.progress_bar = QProgressBar(self)
  293. self.progress_bar.setRange(0, 100)
  294. self.progress_bar.setValue(0)
  295. self.progress_bar.setTextVisible(False)
  296. self.progress_bar.setFixedHeight(10)
  297. self.progress_bar.setStyleSheet("""
  298. QProgressBar {
  299. background-color: #001824;
  300. border: 1px solid #004080;
  301. border-radius: 5px;
  302. margin: 10px 100px;
  303. }
  304. QProgressBar::chunk {
  305. background-color: qlineargradient(x1:0, y1:0.5, x2:1, y2:0.5, stop:0 #00ccff, stop:1 #00ffcc);
  306. border-radius: 5px;
  307. }
  308. """)
  309. self.layout.addWidget(self.progress_bar)
  310. # 添加空白占位
  311. self.layout.addStretch(2)
  312. # 添加版本信息
  313. self.version_label = QLabel("版本 1.0.0 | © 2025 火眼金睛灾害监测技术实验室", self)
  314. self.version_label.setAlignment(Qt.AlignCenter)
  315. self.version_label.setStyleSheet("color: #4db8ff; font-size: 14px; margin-bottom: 20px;")
  316. self.layout.addWidget(self.version_label)
  317. # 添加底部说明
  318. self.bottom_label = QLabel("正在启动...", self)
  319. self.bottom_label.setAlignment(Qt.AlignCenter)
  320. self.bottom_label.setStyleSheet("color: #6698ff; font-size: 12px; margin-bottom: 10px;")
  321. self.layout.addWidget(self.bottom_label)
  322. # 添加背景效果
  323. self.circuit_effect = CircuitEffect(self)
  324. self.circuit_effect.setGeometry(0, 0, self.screen_width, self.screen_height)
  325. # 添加粒子效果
  326. self.particle_effect = ParticleEffect(self)
  327. self.particle_effect.setGeometry(0, 0, self.screen_width, self.screen_height)
  328. # 初始化进度计时器
  329. self.progress_timer = QTimer(self)
  330. self.progress_timer.timeout.connect(self.update_progress)
  331. self.current_progress = 0
  332. # 状态消息列表
  333. self.status_messages = [
  334. "加载核心模块...",
  335. "初始化灾害检测模型...",
  336. "配置多模态数据源...",
  337. "加载地理信息系统...",
  338. "初始化摄像头监控模块...",
  339. "配置告警系统...",
  340. "连接云端数据中心...",
  341. "准备AI分析引擎...",
  342. "系统启动完成,正在进入主界面..."
  343. ]
  344. self.current_message_index = 0
  345. # 效果定时器
  346. self.effect_timer = QTimer(self)
  347. self.effect_timer.timeout.connect(self.update_effects)
  348. self.effect_timer.start(100)
  349. # 效果变量
  350. self.title_phase = 0
  351. self.glow_value = 0
  352. self.glow_direction = 1
  353. def update_effects(self):
  354. """更新视觉效果"""
  355. # 标题发光效果
  356. self.glow_value += self.glow_direction * 2
  357. if self.glow_value > 100:
  358. self.glow_value = 100
  359. self.glow_direction = -1
  360. elif self.glow_value < 0:
  361. self.glow_value = 0
  362. self.glow_direction = 1
  363. glow_color = f"rgba(0, {180 + self.glow_value//2}, {220 + self.glow_value//3}, 0.8)"
  364. self.title_label.setStyleSheet(f"color: #00e6e6; margin-top: 20px; text-shadow: 0 0 15px {glow_color};")
  365. # 底部提示变化
  366. self.title_phase += 1
  367. if self.title_phase % 50 == 0:
  368. dots = "." * ((self.title_phase // 10) % 4)
  369. self.bottom_label.setText(f"正在启动{dots}")
  370. def start_loading(self, duration=5000):
  371. """开始加载动画,持续duration毫秒"""
  372. self.progress_timer.start(duration / 100) # 将持续时间分成100步
  373. def update_progress(self):
  374. """更新进度和消息"""
  375. self.current_progress += 1
  376. self.progress_bar.setValue(self.current_progress)
  377. # 更新状态消息
  378. if self.current_progress % (100 // len(self.status_messages)) == 0:
  379. if self.current_message_index < len(self.status_messages):
  380. self.status_label.setText(self.status_messages[self.current_message_index])
  381. self.current_message_index += 1
  382. # 完成加载
  383. if self.current_progress >= 100:
  384. self.progress_timer.stop()
  385. QTimer.singleShot(500, self.finish_loading) # 延迟半秒后完成
  386. def finish_loading(self):
  387. """完成加载后的处理"""
  388. # 在实际应用中,这里可以发出加载完成的信号
  389. pass
  390. def paintEvent(self, event):
  391. """自定义绘制事件"""
  392. super().paintEvent(event)
  393. painter = QPainter(self)
  394. painter.setRenderHint(QPainter.Antialiasing)
  395. # 绘制背景渐变
  396. gradient = QLinearGradient(0, 0, 0, self.height())
  397. gradient.setColorAt(0, QColor(10, 30, 60))
  398. gradient.setColorAt(0.5, QColor(5, 20, 40))
  399. gradient.setColorAt(1, QColor(10, 30, 60))
  400. painter.fillRect(0, 0, self.width(), self.height(), gradient)
  401. # 绘制边框
  402. margin = 10
  403. pen = QPen(QColor(0, 180, 230, 100), 3)
  404. painter.setPen(pen)
  405. painter.drawRect(margin, margin, self.width()-margin*2, self.height()-margin*2)
  406. # 绘制顶部和底部装饰线
  407. painter.setPen(QPen(QColor(0, 180, 230, 150), 2))
  408. # 顶部装饰
  409. top_margin = 30
  410. line_width = self.width() * 0.4
  411. painter.drawLine(self.width()/2 - line_width/2, top_margin,
  412. self.width()/2 + line_width/2, top_margin)
  413. # 左上角装饰
  414. painter.drawLine(margin*2, top_margin*2, margin*6, top_margin*2)
  415. painter.drawLine(margin*2, top_margin*2, margin*2, top_margin*5)
  416. # 右上角装饰
  417. painter.drawLine(self.width()-margin*6, top_margin*2, self.width()-margin*2, top_margin*2)
  418. painter.drawLine(self.width()-margin*2, top_margin*2, self.width()-margin*2, top_margin*5)
  419. # 底部装饰
  420. bottom_margin = 30
  421. painter.drawLine(self.width()/2 - line_width/2, self.height()-bottom_margin,
  422. self.width()/2 + line_width/2, self.height()-bottom_margin)
  423. # 左下角装饰
  424. painter.drawLine(margin*2, self.height()-top_margin*2, margin*6, self.height()-top_margin*2)
  425. painter.drawLine(margin*2, self.height()-top_margin*2, margin*2, self.height()-top_margin*5)
  426. # 右下角装饰
  427. painter.drawLine(self.width()-margin*6, self.height()-top_margin*2, self.width()-margin*2, self.height()-top_margin*2)
  428. painter.drawLine(self.width()-margin*2, self.height()-top_margin*2, self.width()-margin*2, self.height()-top_margin*5)
  429. def show_splash_screen(app, main_window, duration=5000):
  430. """显示启动屏幕
  431. Args:
  432. app: QApplication实例
  433. main_window: 主窗口实例
  434. duration: 显示启动屏幕的时间(毫秒)
  435. """
  436. splash = CustomSplashScreen()
  437. splash.show()
  438. # 开始加载动画
  439. splash.start_loading(duration)
  440. # 设置计时器,在指定时间后隐藏启动屏幕并显示主窗口
  441. QTimer.singleShot(duration, lambda: finish_splash(splash, main_window))
  442. # 处理事件,确保启动屏幕显示
  443. app.processEvents()
  444. def finish_splash(splash, main_window):
  445. """完成启动屏幕并显示主窗口"""
  446. # 淡出效果
  447. fade_out = QPropertyAnimation(splash, b"windowOpacity")
  448. fade_out.setDuration(1000) # 延长淡出时间
  449. fade_out.setStartValue(1.0)
  450. fade_out.setEndValue(0.0)
  451. fade_out.setEasingCurve(QEasingCurve.OutQuad)
  452. fade_out.finished.connect(splash.close)
  453. fade_out.start()
  454. # 显示主窗口
  455. main_window.show()
  456. # 测试代码
  457. if __name__ == "__main__":
  458. app = QApplication(sys.argv)
  459. # 创建一个简单的主窗口用于测试
  460. main = QWidget()
  461. main.setWindowTitle("测试主窗口")
  462. main.resize(800, 600)
  463. # 显示启动屏幕
  464. show_splash_screen(app, main, 5000)
  465. sys.exit(app.exec_())