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) self.tree_indent = 20 # 树形缩进距离 self.expanded_items = set() # 跟踪展开状态 def paint(self, painter: QPainter, option, index): painter.setRenderHint(QPainter.RenderHint.Antialiasing) painter.save() # 获取数据 data = index.data(Qt.ItemDataRole.UserRole) is_expanded = id(data) in self.expanded_items # 绘制卡片背景 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) # 绘制展开/折叠图标 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)) painter.setPen(Qt.GlobalColor.white) painter.drawText(name_rect, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, data.name) date_rect = QRect(name_rect) date_rect.translate(0, name_rect.height()) painter.setFont(QFont("Microsoft YaHei", 9)) painter.setPen(QColor(180, 180, 180)) painter.drawText(date_rect, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, data.date) desc_rect = QRect(date_rect) desc_rect.translate(0, date_rect.height()) desc_rect.setHeight(40) painter.setPen(QColor(160, 160, 160)) painter.drawText(desc_rect, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop | Qt.TextFlag.TextWordWrap, data.description) # 如果展开,绘制参数 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) painter.restore() def sizeHint(self, option, index): 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