2025-02-18 22:05:52 +08:00
|
|
|
from PySide6.QtWidgets import (QWidget, QListWidget, QStyledItemDelegate,
|
|
|
|
QApplication, QVBoxLayout, QMenu, QListWidgetItem,
|
|
|
|
QStyle)
|
|
|
|
from PySide6.QtCore import Qt, QSize, QRect, QPoint
|
|
|
|
from PySide6.QtGui import (QPainter, QPainterPath, QColor, QLinearGradient,
|
|
|
|
QPen, QFont, QIcon)
|
|
|
|
|
|
|
|
|
|
|
|
class CardItemDelegate(QStyledItemDelegate):
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
super().__init__(parent)
|
2025-02-23 18:52:15 +08:00
|
|
|
self.tree_indent = 20 # 树形缩进距离
|
|
|
|
self.expanded_items = set() # 跟踪展开状态
|
2025-02-18 22:05:52 +08:00
|
|
|
|
|
|
|
def paint(self, painter: QPainter, option, index):
|
|
|
|
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
|
|
painter.save()
|
|
|
|
|
|
|
|
# 获取数据
|
|
|
|
data = index.data(Qt.ItemDataRole.UserRole)
|
2025-02-23 18:52:15 +08:00
|
|
|
is_expanded = id(data) in self.expanded_items
|
2025-02-18 22:05:52 +08:00
|
|
|
|
|
|
|
# 绘制卡片背景
|
|
|
|
rect = option.rect
|
|
|
|
rect.adjust(8, 4, -8, -4)
|
|
|
|
|
|
|
|
# 创建圆角路径
|
|
|
|
path = QPainterPath()
|
|
|
|
path.addRoundedRect(rect, 8, 8)
|
|
|
|
|
|
|
|
# 绘制阴影
|
|
|
|
shadow_color = QColor(0, 0, 0, 30)
|
|
|
|
for i in range(5):
|
|
|
|
shadow_rect = rect.adjusted(0, i, 0, i)
|
|
|
|
shadow_path = QPainterPath()
|
|
|
|
shadow_path.addRoundedRect(shadow_rect, 8, 8)
|
|
|
|
painter.fillPath(shadow_path, shadow_color)
|
|
|
|
|
|
|
|
# 绘制卡片背景
|
|
|
|
gradient = QLinearGradient(rect.topLeft(), rect.bottomLeft())
|
|
|
|
if data.activated:
|
|
|
|
gradient.setColorAt(0, QColor(40, 70, 45))
|
|
|
|
gradient.setColorAt(1, QColor(45, 80, 50))
|
|
|
|
painter.fillPath(path, gradient)
|
|
|
|
painter.setPen(QPen(QColor(60, 180, 90), 2))
|
|
|
|
elif option.state & QStyle.State_Selected:
|
|
|
|
gradient.setColorAt(0, QColor(45, 45, 55))
|
|
|
|
gradient.setColorAt(1, QColor(55, 55, 65))
|
|
|
|
painter.fillPath(path, gradient)
|
|
|
|
painter.setPen(QPen(QColor(70, 130, 180), 2))
|
|
|
|
else:
|
|
|
|
painter.fillPath(path, QColor(35, 35, 40))
|
|
|
|
painter.setPen(QPen(QColor(60, 60, 65)))
|
|
|
|
|
|
|
|
painter.drawPath(path)
|
|
|
|
|
|
|
|
# 计算文本区域
|
|
|
|
padding = 15
|
|
|
|
name_rect = rect.adjusted(padding, padding, -padding, 0)
|
|
|
|
name_rect.setHeight(25)
|
|
|
|
|
2025-02-23 18:52:15 +08:00
|
|
|
# 绘制展开/折叠图标
|
|
|
|
if data.params:
|
|
|
|
icon_size = 16
|
|
|
|
icon_rect = QRect(
|
|
|
|
name_rect.left(),
|
|
|
|
name_rect.top() + (name_rect.height() - icon_size) // 2,
|
|
|
|
icon_size,
|
|
|
|
icon_size
|
|
|
|
)
|
|
|
|
|
|
|
|
painter.setPen(QPen(QColor(160, 160, 160), 1))
|
|
|
|
painter.setBrush(Qt.BrushStyle.NoBrush)
|
|
|
|
|
|
|
|
# 绘制小方框
|
|
|
|
painter.drawRect(icon_rect)
|
|
|
|
|
|
|
|
# 绘制横线
|
|
|
|
h_line_y = icon_rect.center().y()
|
|
|
|
painter.drawLine(
|
|
|
|
icon_rect.left() + 3,
|
|
|
|
h_line_y,
|
|
|
|
icon_rect.right() - 3,
|
|
|
|
h_line_y
|
|
|
|
)
|
|
|
|
|
|
|
|
# 绘制竖线(如果未展开)
|
|
|
|
if not is_expanded:
|
|
|
|
v_line_x = icon_rect.center().x()
|
|
|
|
painter.drawLine(
|
|
|
|
v_line_x,
|
|
|
|
icon_rect.top() + 3,
|
|
|
|
v_line_x,
|
|
|
|
icon_rect.bottom() - 3
|
|
|
|
)
|
|
|
|
|
|
|
|
name_rect.setLeft(icon_rect.right() + 5)
|
|
|
|
|
|
|
|
# 绘制基本信息
|
|
|
|
painter.setFont(QFont("Microsoft YaHei", 10, QFont.Weight.Bold))
|
2025-02-18 22:05:52 +08:00
|
|
|
painter.setPen(Qt.GlobalColor.white)
|
|
|
|
painter.drawText(name_rect, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, data.name)
|
|
|
|
|
2025-02-23 18:52:15 +08:00
|
|
|
date_rect = QRect(name_rect)
|
|
|
|
date_rect.translate(0, name_rect.height())
|
|
|
|
painter.setFont(QFont("Microsoft YaHei", 9))
|
2025-02-18 22:05:52 +08:00
|
|
|
painter.setPen(QColor(180, 180, 180))
|
|
|
|
painter.drawText(date_rect, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, data.date)
|
|
|
|
|
2025-02-23 18:52:15 +08:00
|
|
|
desc_rect = QRect(date_rect)
|
|
|
|
desc_rect.translate(0, date_rect.height())
|
|
|
|
desc_rect.setHeight(40)
|
2025-02-18 22:05:52 +08:00
|
|
|
painter.setPen(QColor(160, 160, 160))
|
|
|
|
painter.drawText(desc_rect, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop | Qt.TextFlag.TextWordWrap,
|
|
|
|
data.description)
|
|
|
|
|
2025-02-23 18:52:15 +08:00
|
|
|
# 如果展开,绘制参数
|
|
|
|
if is_expanded and data.params:
|
|
|
|
param_start_y = desc_rect.bottom() + 10
|
|
|
|
param_font = QFont("Microsoft YaHei", 9)
|
|
|
|
painter.setFont(param_font)
|
|
|
|
|
|
|
|
for i, param in enumerate(data.params):
|
|
|
|
param_rect = QRect(
|
|
|
|
name_rect.left() + self.tree_indent,
|
|
|
|
param_start_y + i * 25,
|
|
|
|
rect.width() - 2 * padding - self.tree_indent,
|
|
|
|
20
|
|
|
|
)
|
|
|
|
|
|
|
|
# 绘制连接线
|
|
|
|
line_start_x = name_rect.left() + self.tree_indent // 2
|
|
|
|
line_end_x = param_rect.left()
|
|
|
|
line_y = param_rect.center().y()
|
|
|
|
|
|
|
|
painter.setPen(QPen(QColor(80, 80, 80), 1))
|
|
|
|
# 绘制垂直线
|
|
|
|
if i == 0:
|
|
|
|
painter.drawLine(
|
|
|
|
line_start_x,
|
|
|
|
desc_rect.bottom() + 5,
|
|
|
|
line_start_x,
|
|
|
|
param_start_y + (len(data.params) - 1) * 25 + 10
|
|
|
|
)
|
|
|
|
|
|
|
|
# 绘制水平线
|
|
|
|
painter.drawLine(
|
|
|
|
line_start_x,
|
|
|
|
line_y,
|
|
|
|
line_end_x,
|
|
|
|
line_y
|
|
|
|
)
|
|
|
|
|
|
|
|
# 绘制参数文本
|
|
|
|
painter.setPen(QColor(140, 140, 140))
|
|
|
|
param_text = f"{param.name}: {param.value}"
|
|
|
|
painter.drawText(param_rect, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, param_text)
|
|
|
|
|
2025-02-18 22:05:52 +08:00
|
|
|
painter.restore()
|
|
|
|
|
|
|
|
def sizeHint(self, option, index):
|
2025-02-23 18:52:15 +08:00
|
|
|
data = index.data(Qt.ItemDataRole.UserRole)
|
|
|
|
base_height = 130
|
|
|
|
|
|
|
|
if id(data) in self.expanded_items and data.params:
|
|
|
|
param_height = len(data.params) * 25
|
|
|
|
return QSize(380, base_height + param_height + 10)
|
|
|
|
|
|
|
|
return QSize(380, base_height)
|
|
|
|
|
|
|
|
def editorEvent(self, event, model, option, index):
|
|
|
|
if event.type() == event.Type.MouseButtonPress:
|
|
|
|
data = index.data(Qt.ItemDataRole.UserRole)
|
|
|
|
if not data.params:
|
|
|
|
return False
|
|
|
|
|
|
|
|
rect = option.rect
|
|
|
|
padding = 15
|
|
|
|
icon_size = 16
|
|
|
|
icon_rect = QRect(
|
|
|
|
rect.left() + padding,
|
|
|
|
rect.top() + padding + (25 - icon_size) // 2,
|
|
|
|
icon_size,
|
|
|
|
icon_size
|
|
|
|
)
|
|
|
|
|
|
|
|
if icon_rect.contains(event.pos()):
|
|
|
|
if id(data) in self.expanded_items:
|
|
|
|
self.expanded_items.remove(id(data))
|
|
|
|
else:
|
|
|
|
self.expanded_items.add(id(data))
|
|
|
|
|
|
|
|
model.dataChanged.emit(index, index)
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|