Files
nl-utils/test/t.py

994 lines
32 KiB
Python
Raw Normal View History

2025-06-07 23:43:57 +08:00
import math
import random
from PyQt5.QtCore import Qt, QPropertyAnimation, QRectF, QPoint, QTimer, QParallelAnimationGroup, pyqtProperty, \
QEasingCurve, QSize, pyqtSignal, QPointF
from PyQt5.QtGui import QPixmap, QIcon, QColor, QPainter, QLinearGradient
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QFrame, QGridLayout, QSpacerItem,
QSizePolicy, QScrollArea, QSizeGrip, QLineEdit)
class AnimatedCard(QFrame):
"""具有动画效果的卡片控件"""
def __init__(self, app_data, parent=None):
super().__init__(parent)
self.app_data = app_data
self.start_color = QColor(app_data["color"][0])
self.end_color = QColor(app_data["color"][1])
self._angle = 0
self.hover_state = False
self.init_animations()
self.setMinimumSize(300, 320)
self.setMaximumSize(300, 320)
self.setCursor(Qt.PointingHandCursor)
self.setStyleSheet("""
AnimatedCard {
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
}
""")
def init_animations(self):
# 渐变角度动画
self.angle_animation = QPropertyAnimation(self, b"angle")
self.angle_animation.setDuration(20000) # 20秒完成一轮
self.angle_animation.setStartValue(0)
self.angle_animation.setEndValue(360)
self.angle_animation.setLoopCount(-1) # 无限循环
self.angle_animation.start()
# 悬停动画组
self.hover_animation = QPropertyAnimation(self, b"geometry")
self.hover_animation.setDuration(300)
self.hover_animation.setEasingCurve(QEasingCurve.OutBack)
# 阴影动画
self.shadow_animation = QPropertyAnimation(self, b"styleSheet")
self.shadow_animation.setDuration(300)
# 并行动画组
self.animation_group = QParallelAnimationGroup(self)
self.animation_group.addAnimation(self.hover_animation)
self.animation_group.addAnimation(self.shadow_animation)
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(Qt.NoPen)
# 创建主渐变背景
rect = self.rect()
# 添加动态渐变背景
if self.hover_state:
# 悬停状态使用更亮的渐变
gradient = QLinearGradient(rect.topLeft(), rect.bottomRight())
gradient.setColorAt(0, self.start_color.lighter(120))
gradient.setColorAt(1, self.end_color.lighter(120))
else:
# 普通状态
gradient = QLinearGradient(rect.topLeft(), rect.bottomRight())
gradient.setColorAt(0, self.start_color)
gradient.setColorAt(1, self.end_color)
# 添加动态旋转效果
cx = rect.center().x()
cy = rect.center().y()
radius = max(rect.width(), rect.height()) * 1.2
start_x = cx + radius * 0.5 * math.cos(math.radians(self.angle))
start_y = cy + radius * 0.5 * math.sin(math.radians(self.angle))
end_x = cx - radius * 0.5 * math.cos(math.radians(self.angle))
end_y = cy - radius * 0.5 * math.sin(math.radians(self.angle))
moving_gradient = QLinearGradient(start_x, start_y, end_x, end_y)
moving_gradient.setColorAt(0, QColor(255, 255, 255, 30))
moving_gradient.setColorAt(1, QColor(255, 255, 255, 10))
# 绘制背景
painter.setBrush(gradient)
painter.drawRoundedRect(rect, 16, 16)
# 绘制动态效果层
painter.setBrush(moving_gradient)
painter.drawRoundedRect(rect, 16, 16)
# 绘制高光边框
if self.hover_state:
border_gradient = QLinearGradient(rect.topLeft(), rect.topRight())
border_gradient.setColorAt(0, QColor(255, 255, 255, 100))
border_gradient.setColorAt(1, QColor(255, 255, 255, 30))
painter.setPen(QColor(255, 255, 255, 150))
painter.drawRoundedRect(rect.adjusted(1, 1, -1, -1), 16, 16)
def enterEvent(self, event):
self.hover_state = True
self.animate_hover(True)
self.update()
super().enterEvent(event)
def leaveEvent(self, event):
self.hover_state = False
self.animate_hover(False)
self.update()
super().leaveEvent(event)
def animate_hover(self, hover):
"""执行悬停动画"""
# 保存当前位置和大小
current_geometry = self.geometry()
# 目标位置和大小
if hover:
target_geometry = current_geometry.adjusted(-5, -5, 5, 5)
shadow_value = 20
else:
target_geometry = current_geometry.adjusted(5, 5, -5, -5)
shadow_value = 5
# 设置位置动画
self.hover_animation.setStartValue(current_geometry)
self.hover_animation.setEndValue(target_geometry)
# 设置阴影动画
self.shadow_animation.setStartValue(self.styleSheet())
self.shadow_animation.setEndValue(f"""
AnimatedCard {{
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 10px {shadow_value}px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
}}
""")
# 开始动画
self.animation_group.start()
@pyqtProperty(int)
def angle(self):
return self._angle
@angle.setter
def angle(self, value):
self._angle = value
self.update()
class AppDetailWindow(QWidget):
"""应用详情窗口"""
def __init__(self, app_data, parent=None):
super().__init__(parent)
self.app_data = app_data
self.setObjectName("appDetail")
self.setWindowFlags(Qt.FramelessWindowHint | Qt.Popup)
self.setAttribute(Qt.WA_TranslucentBackground)
self.init_ui()
self.setStyleSheet(self.get_styles())
def init_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(20, 20, 20, 20)
layout.setSpacing(0)
# 标题栏
title_bar = QWidget()
title_bar.setObjectName("titleBar")
title_bar_layout = QHBoxLayout(title_bar)
title_bar_layout.setContentsMargins(0, 0, 0, 0)
title = QLabel(self.app_data["title"])
title.setObjectName("detailTitle")
close_btn = QPushButton()
close_btn.setObjectName("closeBtn")
close_btn.setIcon(QIcon(":/icons/close.png"))
close_btn.setIconSize(QSize(16, 16))
close_btn.clicked.connect(self.close)
title_bar_layout.addWidget(title)
title_bar_layout.addWidget(close_btn)
# 内容区域
content = QWidget()
content_layout = QVBoxLayout(content)
content_layout.setContentsMargins(0, 15, 0, 0)
# 应用信息
info_layout = QHBoxLayout()
info_layout.setSpacing(20)
# 左侧 - 图标和基本信息
left_panel = QWidget()
left_layout = QVBoxLayout(left_panel)
left_layout.setSpacing(15)
# 图标
icon_frame = QFrame()
icon_frame.setFixedSize(120, 120)
icon_frame.setObjectName("detailIcon")
icon_layout = QVBoxLayout(icon_frame)
icon = QLabel()
icon_pixmap = QPixmap(f":/icons/{self.app_data['icon']}.png").scaled(80, 80,
Qt.KeepAspectRatio,
Qt.SmoothTransformation)
# 转换图标为白色
painter = QPainter(icon_pixmap)
painter.setCompositionMode(QPainter.CompositionMode_SourceIn)
painter.fillRect(icon_pixmap.rect(), Qt.white)
painter.end()
icon.setPixmap(icon_pixmap)
icon.setAlignment(Qt.AlignCenter)
icon_layout.addWidget(icon)
# 作者信息
author_label = QLabel(f"开发者: {self.app_data['author']}")
author_label.setObjectName("detailAuthor")
# 版本信息
version_label = QLabel(f"版本: {self.app_data['version']}")
version_label.setObjectName("detailVersion")
# 上传时间
upload_label = QLabel(f"上传时间: {self.app_data['upload_time']}")
upload_label.setObjectName("detailUpload")
left_layout.addWidget(icon_frame, 0, Qt.AlignCenter)
left_layout.addWidget(author_label)
left_layout.addWidget(version_label)
left_layout.addWidget(upload_label)
left_layout.addStretch()
# 右侧 - 描述和功能
right_panel = QWidget()
right_layout = QVBoxLayout(right_panel)
right_layout.setSpacing(15)
# 应用描述
desc_label = QLabel(self.app_data["desc"])
desc_label.setObjectName("detailDesc")
desc_label.setWordWrap(True)
# 功能列表
features_label = QLabel("功能特点:")
features_label.setObjectName("detailSection")
features_list = QWidget()
features_layout = QVBoxLayout(features_list)
features_layout.setSpacing(8)
for feature in self.app_data["features"]:
feature_item = QLabel(f"{feature}")
feature_item.setObjectName("featureItem")
feature_item.setWordWrap(True)
features_layout.addWidget(feature_item)
# 操作按钮
btn_layout = QHBoxLayout()
btn_layout.setSpacing(15)
start_btn = QPushButton("立即使用")
start_btn.setObjectName("detailStartBtn")
start_btn.clicked.connect(lambda: self.parent().app_selected.emit(self.app_data["id"]))
cancel_btn = QPushButton("返回")
cancel_btn.setObjectName("detailCancelBtn")
cancel_btn.clicked.connect(self.close)
btn_layout.addWidget(start_btn)
btn_layout.addWidget(cancel_btn)
right_layout.addWidget(desc_label)
right_layout.addWidget(features_label)
right_layout.addWidget(features_list)
right_layout.addStretch()
right_layout.addLayout(btn_layout)
info_layout.addWidget(left_panel)
info_layout.addWidget(right_panel)
content_layout.addLayout(info_layout)
# 添加到主布局
layout.addWidget(title_bar)
layout.addWidget(content)
# 添加大小控制点
size_grip = QSizeGrip(self)
size_grip.setFixedSize(16, 16)
size_grip.setStyleSheet("background: transparent;")
layout.addWidget(size_grip, 0, Qt.AlignBottom | Qt.AlignRight)
# 设置窗口尺寸
self.resize(600, 500)
def get_styles(self):
return """
/* ===== 应用详情样式 ===== */
#appDetail {
background-color: rgba(26, 32, 44, 0.95);
border: 1px solid rgba(74, 85, 104, 0.7);
border-radius: 12px;
box-shadow: 0 15px 50px rgba(0, 0, 0, 0.5);
}
#titleBar {
background-color: rgba(45, 55, 72, 0.7);
border-top-left-radius: 12px;
border-top-right-radius: 12px;
padding: 15px 20px;
}
#detailTitle {
font-size: 24px;
font-weight: bold;
color: #ffffff;
}
#closeBtn {
background-color: transparent;
border: none;
padding: 5px;
border-radius: 4px;
}
#closeBtn:hover {
background-color: rgba(239, 68, 68, 0.2);
}
#detailIcon {
background-color: rgba(255, 255, 255, 0.1);
border-radius: 60px;
}
#detailAuthor,
#detailVersion,
#detailUpload {
font-size: 16px;
color: rgba(255, 255, 255, 0.9);
padding: 5px 10px;
border-radius: 6px;
background-color: rgba(255, 255, 255, 0.05);
}
#detailDesc {
font-size: 17px;
color: rgba(255, 255, 255, 0.85);
line-height: 1.6;
}
#detailSection {
font-size: 19px;
font-weight: 600;
color: #ffffff;
margin-top: 15px;
}
#featureItem {
font-size: 15px;
color: rgba(255, 255, 255, 0.8);
padding-left: 10px;
}
#detailStartBtn {
background-color: #4f46e5;
color: white;
border-radius: 8px;
padding: 12px 30px;
font-size: 16px;
font-weight: 600;
min-width: 140px;
}
#detailStartBtn:hover {
background-color: #4338ca;
}
#detailCancelBtn {
background-color: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.9);
border-radius: 8px;
padding: 12px 30px;
font-size: 16px;
min-width: 100px;
}
#detailCancelBtn:hover {
background-color: rgba(255, 255, 255, 0.2);
}
"""
class AppCenter(QWidget):
"""独立的应用中心页面"""
app_selected = pyqtSignal(str)
def __init__(self):
super().__init__()
self.setObjectName("Home")
self.app_detail_window = None
self.init_ui()
self.setStyleSheet(self.get_styles())
def init_ui(self):
# 主布局
main_layout = QVBoxLayout()
main_layout.setContentsMargins(40, 30, 40, 30)
main_layout.setSpacing(20)
# 标题和搜索区域
header_layout = QHBoxLayout()
title = QLabel("应用中心")
title.setObjectName("pageTitle")
# 搜索框
search_container = QFrame()
search_container.setObjectName("searchContainer")
search_layout = QHBoxLayout(search_container)
search_layout.setContentsMargins(15, 5, 15, 5)
self.search_input = QLineEdit()
self.search_input.setPlaceholderText("搜索应用...")
self.search_input.setObjectName("searchInput")
self.search_input.textChanged.connect(self.filter_apps) # 连接信号到filter_apps方法
search_btn = QPushButton()
search_btn.setObjectName("searchBtn")
search_btn.setIcon(QIcon(":/icons/search.png"))
search_btn.setIconSize(QSize(20, 20))
search_layout.addWidget(self.search_input)
search_layout.addWidget(search_btn)
header_layout.addWidget(title)
header_layout.addStretch()
header_layout.addWidget(search_container)
main_layout.addLayout(header_layout)
# 应用类别过滤
self.category_layout = QHBoxLayout()
self.category_layout.setSpacing(15)
categories = ["全部", "文档处理", "图像处理", "媒体工具", "其他"]
for category in categories:
btn = QPushButton(category)
btn.setObjectName("categoryBtn")
if category == "全部":
btn.setProperty("active", True)
btn.clicked.connect(lambda _, c=category: self.filter_by_category(c))
self.category_layout.addWidget(btn)
self.category_layout.addStretch()
main_layout.addLayout(self.category_layout)
# 应用卡片容器
self.apps_container = QWidget()
self.apps_layout = QGridLayout(self.apps_container)
self.apps_layout.setSpacing(30)
self.apps_layout.setAlignment(Qt.AlignTop)
# 滚动区域
scroll_area = QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_area.setWidget(self.apps_container)
scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
scroll_area.setStyleSheet("border: none; background: transparent;")
main_layout.addWidget(scroll_area)
self.setLayout(main_layout)
# 初始化应用数据
self.apps = self.get_apps_data()
self.display_apps(self.apps)
# 添加背景粒子效果
self.particles = []
self.init_particles()
self.particle_timer = QTimer(self)
self.particle_timer.timeout.connect(self.update_particles)
self.particle_timer.start(30) # 30ms更新一次
def get_styles(self):
return """
/* ===== 应用中心页面样式 ===== */
#Home {
background-color: #1a202c;
}
#pageTitle {
font-size: 28px;
font-weight: bold;
color: #ffffff;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
margin-bottom: 30px;
}
#searchContainer {
background-color: rgba(45, 55, 72, 0.5);
border-radius: 25px;
border: 1px solid rgba(74, 85, 104, 0.5);
}
#searchInput {
background-color: transparent;
border: none;
color: #e2e8f0;
font-size: 15px;
min-width: 250px;
}
#searchInput:focus {
outline: none;
}
#searchBtn {
background-color: transparent;
border: none;
border-radius: 15px;
padding: 5px;
}
#searchBtn:hover {
background-color: rgba(129, 140, 248, 0.2);
}
#categoryBtn {
background-color: rgba(45, 55, 72, 0.5);
color: #a0aec0;
border-radius: 20px;
padding: 8px 20px;
font-size: 14px;
border: none;
transition: all 0.3s ease;
}
#categoryBtn:hover {
background-color: rgba(79, 70, 229, 0.3);
color: #e2e8f0;
}
#categoryBtn[active="true"] {
background-color: #4f46e5;
color: white;
}
/* 应用卡片样式 */
#appCardTitle {
font-size: 22px;
font-weight: 700;
color: white;
text-align: center;
margin-top: 15px;
letter-spacing: 0.5px;
}
#appCardDesc {
font-size: 15px;
color: rgba(255, 255, 255, 0.8);
text-align: center;
padding: 0 15px;
line-height: 1.5;
}
#appCardAuthor {
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
text-align: center;
padding: 0 15px;
}
#appCardDate {
font-size: 13px;
color: rgba(255, 255, 255, 0.6);
text-align: center;
padding: 0 15px;
}
#appCardBtn {
background-color: rgba(255, 255, 255, 0.9);
color: #1a202c;
border-radius: 12px;
padding: 10px 20px;
font-weight: 600;
border: none;
min-width: 120px;
transition: all 0.3s ease;
font-size: 15px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
#appCardBtn:hover {
background-color: white;
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
}
#appCardIcon {
background-color: rgba(255, 255, 255, 0.15);
border-radius: 50%;
transition: all 0.3s ease;
}
#detailBtn {
background-color: transparent;
border: none;
color: rgba(255, 255, 255, 0.7);
font-size: 13px;
padding: 5px;
text-decoration: underline;
}
#detailBtn:hover {
color: white;
}
"""
def get_apps_data(self):
"""获取应用数据"""
return [
{
"id": "word_to_pdf",
"title": "Word转PDF",
"desc": "批量将Word文档转换为PDF格式",
"color": ("#6366F1", "#4F46E5"),
"icon": "word_to_pdf",
"author": "李明",
"upload_time": "2023-06-15",
"version": "1.2.0",
"category": "文档处理",
"features": [
"支持批量转换多个Word文件",
"保持原始文档格式",
"支持DOC和DOCX格式",
"转换速度快,效率高"
]
},
{
"id": "ocr",
"title": "OCR文字识别",
"desc": "从图片中提取文字内容",
"color": ("#4ADE80", "#22C55E"),
"icon": "ocr",
"author": "张伟",
"upload_time": "2023-07-22",
"version": "2.0.1",
"category": "图像处理",
"features": [
"支持多种图片格式",
"识别准确率高",
"支持多语言识别",
"导出为可编辑文本"
]
},
{
"id": "image_convert",
"title": "图片格式转换",
"desc": "转换图像文件格式并优化",
"color": ("#F97316", "#EA580C"),
"icon": "image_convert",
"author": "王芳",
"upload_time": "2023-08-10",
"version": "1.5.3",
"category": "图像处理",
"features": [
"支持JPEG、PNG、WEBP等格式",
"批量转换功能",
"图片压缩优化",
"保持图片质量"
]
},
{
"id": "pdf_merge",
"title": "PDF合并工具",
"desc": "合并多个PDF文件为一个",
"color": ("#8B5CF6", "#7C3AED"),
"icon": "pdf_merge",
"author": "刘强",
"upload_time": "2023-09-05",
"version": "1.1.0",
"category": "文档处理",
"features": [
"拖拽排序PDF文件",
"自定义合并顺序",
"保留原始文档质量",
"支持大文件处理"
]
},
{
"id": "pdf_split",
"title": "PDF拆分工具",
"desc": "拆分PDF文件为多个文档",
"color": ("#EC4899", "#DB2777"),
"icon": "pdf_split",
"author": "陈晓",
"upload_time": "2023-09-18",
"version": "1.0.5",
"category": "文档处理",
"features": [
"按页码拆分PDF",
"按书签拆分PDF",
"自定义拆分范围",
"批量处理多个文件"
]
},
{
"id": "video_compress",
"title": "视频压缩",
"desc": "减小视频文件大小",
"color": ("#3B82F6", "#2563EB"),
"icon": "video_compress",
"author": "赵雷",
"upload_time": "2023-10-02",
"version": "1.3.2",
"category": "媒体工具",
"features": [
"支持多种视频格式",
"自定义压缩参数",
"保持视频质量",
"批量处理功能"
]
},
{
"id": "audio_extract",
"title": "音频提取",
"desc": "从视频中提取音频",
"color": ("#06B6D4", "#0891B2"),
"icon": "audio_extract",
"author": "杨帆",
"upload_time": "2023-10-15",
"version": "1.0.3",
"category": "媒体工具",
"features": [
"支持多种视频格式",
"提取高质量音频",
"批量处理功能",
"快速处理速度"
]
},
{
"id": "file_encrypt",
"title": "文件加密",
"desc": "保护您的敏感文件",
"color": ("#F59E0B", "#D97706"),
"icon": "file_encrypt",
"author": "周涛",
"upload_time": "2023-10-28",
"version": "1.1.2",
"category": "其他",
"features": [
"AES-256加密算法",
"支持多种文件类型",
"密码保护",
"批量加密功能"
]
},
{
"id": "text_summary",
"title": "文本摘要",
"desc": "自动提取文本核心内容",
"color": ("#10B981", "#059669"),
"icon": "text_summary",
"author": "吴明",
"upload_time": "2023-11-10",
"version": "1.0.1",
"category": "文本处理",
"features": [
"智能提取关键信息",
"支持多种语言",
"可调节摘要长度",
"高精度结果输出"
]
}
]
def display_apps(self, apps):
"""显示应用卡片"""
# 清空现有卡片
for i in reversed(range(self.apps_layout.count())):
widget = self.apps_layout.itemAt(i).widget()
if widget:
widget.deleteLater()
# 添加新卡片
row, col = 0, 0
max_cols = 3 # 每行最多3个卡片
for app in apps:
card = self.create_app_card(app)
self.apps_layout.addWidget(card, row, col)
col += 1
if col >= max_cols:
col = 0
row += 1
# 如果最后一行不满,添加空白占位
if col != 0 and col < max_cols:
for i in range(col, max_cols):
spacer = QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)
self.apps_layout.addItem(spacer, row, i)
def create_app_card(self, app):
"""创建应用卡片"""
card = AnimatedCard(app)
card.setObjectName("appCard")
layout = QVBoxLayout(card)
layout.setContentsMargins(25, 25, 25, 20)
layout.setSpacing(15)
# 图标
icon_frame = QFrame()
icon_frame.setFixedSize(90, 90)
icon_frame.setObjectName("appCardIcon")
icon_layout = QVBoxLayout(icon_frame)
icon_layout.setContentsMargins(0, 0, 0, 0)
icon = QLabel()
icon_pixmap = QPixmap(f":/icons/{app['icon']}.png").scaled(56, 56,
Qt.KeepAspectRatio,
Qt.SmoothTransformation)
# 转换图标为白色
painter = QPainter(icon_pixmap)
painter.setCompositionMode(QPainter.CompositionMode_SourceIn)
painter.fillRect(icon_pixmap.rect(), Qt.white)
painter.end()
icon.setPixmap(icon_pixmap)
icon.setAlignment(Qt.AlignCenter)
icon_layout.addWidget(icon)
layout.addWidget(icon_frame, 0, Qt.AlignHCenter)
# 标题
title = QLabel(app["title"])
title.setObjectName("appCardTitle")
layout.addWidget(title)
# 描述
desc = QLabel(app["desc"])
desc.setObjectName("appCardDesc")
desc.setWordWrap(True)
layout.addWidget(desc)
# 作者信息
author = QLabel(f"开发者: {app['author']}")
author.setObjectName("appCardAuthor")
layout.addWidget(author)
# 上传时间
date = QLabel(f"上传时间: {app['upload_time']}")
date.setObjectName("appCardDate")
layout.addWidget(date)
# 添加伸缩空间保持底部对齐
layout.addStretch()
# 按钮区域
btn_layout = QHBoxLayout()
btn_layout.setSpacing(10)
# 开始使用按钮
start_btn = QPushButton("开始使用")
start_btn.setObjectName("appCardBtn")
start_btn.setCursor(Qt.PointingHandCursor)
start_btn.clicked.connect(lambda: self.app_selected.emit(app["id"]))
# 详情按钮
detail_btn = QPushButton("详情")
detail_btn.setObjectName("detailBtn")
detail_btn.setCursor(Qt.PointingHandCursor)
detail_btn.clicked.connect(lambda: self.show_app_detail(app))
btn_layout.addWidget(start_btn)
btn_layout.addWidget(detail_btn)
layout.addLayout(btn_layout)
return card
def init_particles(self):
"""初始化背景粒子"""
for _ in range(20):
particle = {
'x': random.randint(0, self.width()),
'y': random.randint(0, self.height()),
'size': random.randint(2, 6),
'speed': random.uniform(0.5, 2),
'color': random.choice([
QColor(79, 70, 229, 80),
QColor(129, 140, 248, 60),
QColor(59, 130, 246, 70),
QColor(16, 185, 129, 60)
])
}
self.particles.append(particle)
def update_particles(self):
"""更新粒子位置"""
if not self.isVisible():
return
for particle in self.particles:
particle['y'] += particle['speed']
if particle['y'] > self.height():
particle['y'] = 0
particle['x'] = random.randint(0, self.width())
self.update()
def paintEvent(self, event):
"""绘制背景粒子效果"""
super().paintEvent(event)
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(Qt.NoPen)
# 绘制粒子
for particle in self.particles:
painter.setBrush(particle['color'])
painter.drawEllipse(QPointF(particle['x'], particle['y']), particle['size'], particle['size'])
def filter_apps(self):
"""根据搜索关键词过滤应用"""
keyword = self.search_input.text().strip().lower()
if not keyword:
self.display_apps(self.apps)
return
filtered_apps = [
app for app in self.apps
if (keyword in app["title"].lower() or
keyword in app["desc"].lower() or
keyword in app["author"].lower() or
keyword in app["category"].lower())
]
self.display_apps(filtered_apps)
def filter_by_category(self, category):
"""根据类别过滤应用"""
# 更新按钮状态
for i in range(self.category_layout.count()):
btn = self.category_layout.itemAt(i).widget()
if btn:
btn.setProperty("active", btn.text() == category)
# 重新应用样式
btn.style().unpolish(btn)
btn.style().polish(btn)
if category == "全部":
self.display_apps(self.apps)
return
filtered_apps = [app for app in self.apps if app["category"] == category]
self.display_apps(filtered_apps)
def show_app_detail(self, app_data):
"""显示应用详情窗口"""
# 关闭现有详情窗口
if self.app_detail_window:
self.app_detail_window.close()
self.app_detail_window = AppDetailWindow(app_data)
self.app_detail_window.setParent(self)
# 计算位置 - 居中显示
parent_rect = self.geometry()
x = parent_rect.x() + (parent_rect.width() - self.app_detail_window.width()) // 2
y = parent_rect.y() + (parent_rect.height() - self.app_detail_window.height()) // 2
self.app_detail_window.move(x, y)
self.app_detail_window.show()