195 lines
5.8 KiB
Python
195 lines
5.8 KiB
Python
"""
|
||
Base widget components for the dashboard.
|
||
"""
|
||
|
||
from typing import Any, Optional, Callable
|
||
|
||
try:
|
||
from PyQt6.QtWidgets import (
|
||
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
|
||
QFrame, QSizePolicy
|
||
)
|
||
from PyQt6.QtCore import Qt, pyqtSignal
|
||
from PyQt6.QtGui import QFont
|
||
HAS_QT = True
|
||
except ImportError:
|
||
HAS_QT = False
|
||
QWidget = object
|
||
pyqtSignal = lambda *a, **k: None
|
||
|
||
|
||
class WidgetHeader(QFrame if HAS_QT else object):
|
||
"""Header component for widgets with title and controls."""
|
||
|
||
collapsed_changed = pyqtSignal(bool) if HAS_QT else None
|
||
close_requested = pyqtSignal() if HAS_QT else None
|
||
|
||
def __init__(self, title: str = "Widget", icon: str = "📦", parent=None):
|
||
if not HAS_QT:
|
||
return
|
||
super().__init__(parent)
|
||
self._collapsed = False
|
||
self._setup_ui(title, icon)
|
||
|
||
def _setup_ui(self, title: str, icon: str):
|
||
self.setFrameShape(QFrame.Shape.StyledPanel)
|
||
self.setStyleSheet("""
|
||
WidgetHeader {
|
||
background-color: #2d2d2d;
|
||
border-radius: 8px 8px 0 0;
|
||
padding: 8px;
|
||
}
|
||
""")
|
||
|
||
layout = QHBoxLayout(self)
|
||
layout.setContentsMargins(10, 5, 10, 5)
|
||
layout.setSpacing(10)
|
||
|
||
# Icon
|
||
self.icon_label = QLabel(icon)
|
||
self.icon_label.setStyleSheet("font-size: 16px;")
|
||
layout.addWidget(self.icon_label)
|
||
|
||
# Title
|
||
self.title_label = QLabel(title)
|
||
font = QFont()
|
||
font.setBold(True)
|
||
self.title_label.setFont(font)
|
||
self.title_label.setStyleSheet("color: #ffffff;")
|
||
layout.addWidget(self.title_label)
|
||
|
||
layout.addStretch()
|
||
|
||
# Collapse button
|
||
self.collapse_btn = QPushButton("▼")
|
||
self.collapse_btn.setFixedSize(24, 24)
|
||
self.collapse_btn.setStyleSheet("""
|
||
QPushButton {
|
||
background-color: #3d3d3d;
|
||
color: #ffffff;
|
||
border: none;
|
||
border-radius: 4px;
|
||
}
|
||
QPushButton:hover {
|
||
background-color: #4d4d4d;
|
||
}
|
||
""")
|
||
self.collapse_btn.clicked.connect(self._toggle_collapse)
|
||
layout.addWidget(self.collapse_btn)
|
||
|
||
# Close button
|
||
self.close_btn = QPushButton("×")
|
||
self.close_btn.setFixedSize(24, 24)
|
||
self.close_btn.setStyleSheet("""
|
||
QPushButton {
|
||
background-color: #3d3d3d;
|
||
color: #ff6b6b;
|
||
border: none;
|
||
border-radius: 4px;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
}
|
||
QPushButton:hover {
|
||
background-color: #ff6b6b;
|
||
color: #ffffff;
|
||
}
|
||
""")
|
||
self.close_btn.clicked.connect(lambda: self.close_requested.emit())
|
||
layout.addWidget(self.close_btn)
|
||
|
||
def _toggle_collapse(self):
|
||
self._collapsed = not self._collapsed
|
||
self.collapse_btn.setText("▶" if self._collapsed else "▼")
|
||
self.collapsed_changed.emit(self._collapsed)
|
||
|
||
def set_collapsed(self, collapsed: bool):
|
||
self._collapsed = collapsed
|
||
self.collapse_btn.setText("▶" if collapsed else "▼")
|
||
|
||
|
||
class WidgetContent(QFrame if HAS_QT else object):
|
||
"""Content container for widgets."""
|
||
|
||
def __init__(self, parent=None):
|
||
if not HAS_QT:
|
||
return
|
||
super().__init__(parent)
|
||
self.setFrameShape(QFrame.Shape.StyledPanel)
|
||
self.setStyleSheet("""
|
||
WidgetContent {
|
||
background-color: #1e1e1e;
|
||
border-radius: 0 0 8px 8px;
|
||
padding: 10px;
|
||
}
|
||
""")
|
||
|
||
self._layout = QVBoxLayout(self)
|
||
self._layout.setContentsMargins(10, 10, 10, 10)
|
||
self._layout.setSpacing(10)
|
||
|
||
def add_widget(self, widget):
|
||
if HAS_QT:
|
||
self._layout.addWidget(widget)
|
||
|
||
def layout(self):
|
||
return self._layout if HAS_QT else None
|
||
|
||
|
||
class DashboardWidget(QFrame if HAS_QT else object):
|
||
"""Complete dashboard widget with header and content."""
|
||
|
||
def __init__(
|
||
self,
|
||
title: str = "Widget",
|
||
icon: str = "📦",
|
||
size: tuple = (300, 200),
|
||
parent=None
|
||
):
|
||
if not HAS_QT:
|
||
return
|
||
super().__init__(parent)
|
||
self._size = size
|
||
self._setup_ui(title, icon)
|
||
|
||
def _setup_ui(self, title: str, icon: str):
|
||
self.setStyleSheet("""
|
||
DashboardWidget {
|
||
background-color: transparent;
|
||
border: 1px solid #3d3d3d;
|
||
border-radius: 8px;
|
||
}
|
||
""")
|
||
|
||
layout = QVBoxLayout(self)
|
||
layout.setContentsMargins(0, 0, 0, 0)
|
||
layout.setSpacing(0)
|
||
|
||
# Header
|
||
self.header = WidgetHeader(title, icon, self)
|
||
self.header.collapsed_changed.connect(self._on_collapse_changed)
|
||
layout.addWidget(self.header)
|
||
|
||
# Content
|
||
self.content = WidgetContent(self)
|
||
layout.addWidget(self.content)
|
||
|
||
self.setFixedSize(*self._size)
|
||
|
||
def _on_collapse_changed(self, collapsed: bool):
|
||
self.content.setVisible(not collapsed)
|
||
if collapsed:
|
||
self.setFixedHeight(self.header.height())
|
||
else:
|
||
self.setFixedHeight(self._size[1])
|
||
|
||
def set_content_widget(self, widget: QWidget):
|
||
"""Set the main content widget."""
|
||
if HAS_QT:
|
||
# Clear existing
|
||
while self.content.layout().count():
|
||
item = self.content.layout().takeAt(0)
|
||
if item.widget():
|
||
item.widget().deleteLater()
|
||
|
||
self.content.layout().addWidget(widget)
|