430 lines
14 KiB
Python
430 lines
14 KiB
Python
import math
|
|
import random
|
|
|
|
from PyQt5.QtCore import Qt, QPropertyAnimation, QRectF, QPoint, QTimer, QParallelAnimationGroup, pyqtSignal, QPointF, \
|
|
pyqtProperty, QEasingCurve
|
|
from PyQt5.QtGui import QPixmap, QIcon, QColor, QPainter, QLinearGradient, QBrush, QRadialGradient, QConicalGradient
|
|
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
|
QPushButton, QFrame, QGridLayout, QSpacerItem, QSizePolicy)
|
|
|
|
|
|
class AnimatedCard(QFrame):
|
|
"""具有动画效果的卡片控件"""
|
|
|
|
def __init__(self, start_color, end_color, parent=None):
|
|
super().__init__(parent)
|
|
self.start_color = QColor(start_color)
|
|
self.end_color = QColor(end_color)
|
|
self.angle = 0
|
|
self.hover_state = False
|
|
self.init_animations()
|
|
self.setMinimumSize(300, 280)
|
|
self.setMaximumSize(300, 280)
|
|
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"shadow")
|
|
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 Home(QWidget):
|
|
app_selected = pyqtSignal(str)
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.init_ui()
|
|
|
|
def init_ui(self):
|
|
self.setObjectName("appCenter")
|
|
self.setStyleSheet(self.get_styles())
|
|
|
|
layout = QVBoxLayout()
|
|
layout.setContentsMargins(40, 30, 40, 30)
|
|
layout.setSpacing(20)
|
|
|
|
# 标题区域
|
|
title_layout = QHBoxLayout()
|
|
|
|
title = QLabel("首页")
|
|
title.setObjectName("pageTitle")
|
|
|
|
title_spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
|
|
|
|
title_layout.addWidget(title)
|
|
title_layout.addSpacerItem(title_spacer)
|
|
|
|
layout.addLayout(title_layout)
|
|
|
|
# 卡片容器
|
|
grid_layout = QVBoxLayout()
|
|
grid_layout.setSpacing(30)
|
|
|
|
# 应用卡片
|
|
apps = [
|
|
{
|
|
"id": "word_to_pdf",
|
|
"title": "Word转PDF",
|
|
"desc": "批量将Word文档转换为PDF格式",
|
|
"color": ("#6366F1", "#4F46E5"), # 紫色
|
|
"icon": "word_to_pdf"
|
|
},
|
|
{
|
|
"id": "ocr",
|
|
"title": "OCR文字识别",
|
|
"desc": "从图片中提取文字内容",
|
|
"color": ("#4ADE80", "#22C55E"), # 绿色
|
|
"icon": "ocr"
|
|
},
|
|
{
|
|
"id": "image_convert",
|
|
"title": "图片格式转换",
|
|
"desc": "转换图像文件格式并优化",
|
|
"color": ("#F97316", "#EA580C"), # 橙色
|
|
"icon": "image_convert"
|
|
},
|
|
{
|
|
"id": "pdf_merge",
|
|
"title": "PDF合并工具",
|
|
"desc": "合并多个PDF文件为一个",
|
|
"color": ("#8B5CF6", "#7C3AED"), # 紫色
|
|
"icon": "pdf_merge"
|
|
},
|
|
{
|
|
"id": "pdf_split",
|
|
"title": "PDF拆分工具",
|
|
"desc": "拆分PDF文件为多个文档",
|
|
"color": ("#EC4899", "#DB2777"), # 粉色
|
|
"icon": "pdf_split"
|
|
},
|
|
{
|
|
"id": "video_compress",
|
|
"title": "视频压缩",
|
|
"desc": "减小视频文件大小",
|
|
"color": ("#3B82F6", "#2563EB"), # 蓝色
|
|
"icon": "video_compress"
|
|
}
|
|
]
|
|
|
|
# 每行显示3张卡片
|
|
for i in range(0, len(apps), 3):
|
|
row_layout = QHBoxLayout()
|
|
row_layout.setSpacing(30)
|
|
row_layout.setAlignment(Qt.AlignCenter)
|
|
|
|
for j in range(3):
|
|
index = i + j
|
|
if index < len(apps):
|
|
row_layout.addWidget(self.create_app_card(apps[index]))
|
|
else:
|
|
# 添加空白占位符保持布局
|
|
spacer = QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)
|
|
row_layout.addSpacerItem(spacer)
|
|
|
|
grid_layout.addLayout(row_layout)
|
|
|
|
layout.addLayout(grid_layout)
|
|
layout.addStretch()
|
|
|
|
self.setLayout(layout)
|
|
|
|
# 添加背景粒子效果
|
|
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 """
|
|
/* ===== 应用中心样式 ===== */
|
|
#appCenter {
|
|
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;
|
|
}
|
|
|
|
#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;
|
|
}
|
|
|
|
#appCardBtn {
|
|
background-color: rgba(255, 255, 255, 0.9);
|
|
color: #1a202c;
|
|
border-radius: 12px;
|
|
padding: 12px 24px;
|
|
font-weight: 600;
|
|
border: none;
|
|
min-width: 140px;
|
|
transition: all 0.3s ease;
|
|
font-size: 16px;
|
|
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;
|
|
}
|
|
"""
|
|
|
|
def create_app_card(self, app):
|
|
"""创建应用卡片"""
|
|
card = AnimatedCard(app["color"][0], app["color"][1])
|
|
card.setObjectName("appCard")
|
|
|
|
layout = QVBoxLayout(card)
|
|
layout.setContentsMargins(25, 25, 25, 25)
|
|
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)
|
|
|
|
# 添加伸缩空间保持底部对齐
|
|
layout.addStretch()
|
|
|
|
# 按钮
|
|
btn_text = "开始使用" if app["id"] != "coming_soon" else "敬请期待"
|
|
btn = QPushButton(btn_text)
|
|
btn.setObjectName("appCardBtn")
|
|
btn.setCursor(Qt.PointingHandCursor)
|
|
|
|
if app["id"] == "coming_soon":
|
|
btn.setEnabled(False)
|
|
btn.setStyleSheet("""
|
|
#appCardBtn:disabled {
|
|
background-color: rgba(255, 255, 255, 0.3);
|
|
color: rgba(26, 32, 44, 0.7);
|
|
}
|
|
""")
|
|
else:
|
|
btn.clicked.connect(lambda: self.app_selected.emit(app["id"]))
|
|
|
|
layout.addWidget(btn, 0, Qt.AlignHCenter)
|
|
|
|
# 为卡片添加点击事件
|
|
card.mousePressEvent = lambda e: self.app_selected.emit(app["id"]) if app["id"] != "coming_soon" else None
|
|
|
|
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):
|
|
"""更新粒子位置"""
|
|
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']) |