EU-Utility/core/dashboard.py

436 lines
14 KiB
Python

"""
EU-Utility - Dashboard Widget System
Customizable dashboard with draggable widgets from plugins.
Each plugin can provide widgets for the dashboard.
"""
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QGridLayout,
QLabel, QPushButton, QFrame, QScrollArea,
QSizePolicy, QGraphicsDropShadowEffect
)
from PyQt6.QtCore import Qt, pyqtSignal, QMimeData
from PyQt6.QtGui import QColor, QDrag, QPainter, QPixmap, QIcon
from PyQt6.QtCore import QSize
from core.icon_manager import get_icon_manager
from core.eu_styles import get_color, EU_TYPOGRAPHY, EU_SIZES
class DashboardWidget(QFrame):
"""Base class for dashboard widgets."""
# Widget metadata
name = "Widget"
description = "Base widget"
icon_name = "target"
size = (1, 1) # Grid size (cols, rows)
def __init__(self, plugin=None, parent=None):
super().__init__(parent)
self.plugin = plugin
self.dragging = False
self.icon_manager = get_icon_manager()
self.setFrameStyle(QFrame.Shape.NoFrame)
self.setStyleSheet("""
DashboardWidget {
background-color: rgba(30, 35, 45, 200);
border: 1px solid rgba(100, 150, 200, 60);
border-radius: 12px;
}
DashboardWidget:hover {
border: 1px solid rgba(100, 180, 255, 100);
}
""")
# Shadow effect
shadow = QGraphicsDropShadowEffect()
shadow.setBlurRadius(20)
shadow.setColor(QColor(0, 0, 0, 80))
shadow.setOffset(0, 4)
self.setGraphicsEffect(shadow)
self._setup_ui()
def _setup_ui(self):
"""Setup widget UI. Override in subclass."""
layout = QVBoxLayout(self)
layout.setContentsMargins(12, 12, 12, 12)
header_layout = QHBoxLayout()
# Icon
icon_label = QLabel()
icon_pixmap = self.icon_manager.get_pixmap(self.icon_name, size=16)
icon_label.setPixmap(icon_pixmap)
icon_label.setFixedSize(16, 16)
header_layout.addWidget(icon_label)
# Title
header = QLabel(self.name)
header.setStyleSheet("""
color: rgba(255, 255, 255, 200);
font-size: 12px;
font-weight: bold;
""")
header_layout.addWidget(header)
header_layout.addStretch()
layout.addLayout(header_layout)
content = QLabel("Widget Content")
content.setStyleSheet("color: rgba(255, 255, 255, 150);")
layout.addWidget(content)
layout.addStretch()
def mousePressEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton:
self.dragging = True
self.drag_start_pos = event.pos()
def mouseMoveEvent(self, event):
if self.dragging and (event.pos() - self.drag_start_pos).manhattanLength() > 10:
self.dragging = False
# Emit signal to parent to handle reordering
if self.parent():
self.parent().start_widget_drag(self)
def mouseReleaseEvent(self, event):
self.dragging = False
def update_data(self, data):
"""Update widget with new data. Override in subclass."""
pass
class SpotifyWidget(DashboardWidget):
"""Spotify now playing widget."""
name = "Now Playing"
description = "Shows current Spotify track"
icon_name = "music"
size = (2, 1)
def __init__(self, plugin=None, parent=None):
self.track_info = None
super().__init__(plugin, parent)
def _setup_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(12, 12, 12, 12)
layout.setSpacing(8)
# Track name
self.track_label = QLabel("Not Playing")
self.track_label.setStyleSheet("""
color: white;
font-size: 13px;
font-weight: bold;
""")
self.track_label.setWordWrap(True)
layout.addWidget(self.track_label)
# Artist
self.artist_label = QLabel("-")
self.artist_label.setStyleSheet("color: rgba(255, 255, 255, 150); font-size: 11px;")
layout.addWidget(self.artist_label)
# Progress bar
self.progress = QLabel("▬▬▬▬▬▬▬▬▬▬")
self.progress.setStyleSheet("color: #1db954; font-size: 10px;")
layout.addWidget(self.progress)
def update_data(self, track_data):
"""Update with track info."""
if track_data:
self.track_label.setText(track_data.get('name', 'Unknown'))
self.track_label.setStyleSheet("""
color: #1db954;
font-size: 13px;
font-weight: bold;
""")
artists = track_data.get('artists', [])
self.artist_label.setText(', '.join(artists) if artists else '-')
class SkillProgressWidget(DashboardWidget):
"""Skill progress tracker widget."""
name = "Skill Progress"
description = "Track skill gains"
icon_name = "bar-chart"
size = (1, 1)
def _setup_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(12, 12, 12, 12)
layout.setSpacing(6)
header_layout = QHBoxLayout()
# Icon
icon_label = QLabel()
icon_pixmap = self.icon_manager.get_pixmap(self.icon_name, size=16)
icon_label.setPixmap(icon_pixmap)
icon_label.setFixedSize(16, 16)
header_layout.addWidget(icon_label)
header = QLabel("Recent Gains")
header.setStyleSheet("color: rgba(255, 255, 255, 200); font-size: 12px; font-weight: bold;")
header_layout.addWidget(header)
header_layout.addStretch()
layout.addLayout(header_layout)
self.gains_list = QLabel("No gains yet")
self.gains_list.setStyleSheet("color: rgba(255, 255, 255, 150); font-size: 10px;")
self.gains_list.setWordWrap(True)
layout.addWidget(self.gains_list)
layout.addStretch()
def update_data(self, gains):
"""Update with recent skill gains."""
if gains:
text = "\n".join([f"+{g['points']} {g['skill']}" for g in gains[:3]])
self.gains_list.setText(text)
class PEDTrackerWidget(DashboardWidget):
"""PED balance tracker widget."""
name = "PED Balance"
description = "Track PED from inventory scans"
icon_name = "ped"
size = (1, 1)
def _setup_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(12, 12, 12, 12)
layout.setSpacing(6)
header_layout = QHBoxLayout()
# Icon
icon_label = QLabel()
icon_pixmap = self.icon_manager.get_pixmap(self.icon_name, size=16)
icon_label.setPixmap(icon_pixmap)
icon_label.setFixedSize(16, 16)
header_layout.addWidget(icon_label)
header = QLabel("PED Balance")
header.setStyleSheet("color: rgba(255, 255, 255, 200); font-size: 12px; font-weight: bold;")
header_layout.addWidget(header)
header_layout.addStretch()
layout.addLayout(header_layout)
self.balance_label = QLabel("0.00 PED")
self.balance_label.setStyleSheet("""
color: #ffc107;
font-size: 18px;
font-weight: bold;
""")
layout.addWidget(self.balance_label)
self.change_label = QLabel("Scan inventory to update")
self.change_label.setStyleSheet("color: rgba(255, 255, 255, 100); font-size: 10px;")
layout.addWidget(self.change_label)
layout.addStretch()
def update_data(self, balance_data):
"""Update with balance info."""
if balance_data:
self.balance_label.setText(f"{balance_data['ped']:.2f} PED")
change = balance_data.get('change', 0)
if change != 0:
color = "#4caf50" if change > 0 else "#f44336"
sign = "+" if change > 0 else ""
self.change_label.setText(f"<span style='color: {color}'>{sign}{change:.2f}</span>")
class QuickActionsWidget(DashboardWidget):
"""Quick action buttons widget."""
name = "Quick Actions"
description = "One-click actions"
icon_name = "zap"
size = (2, 1)
def __init__(self, actions=None, parent=None):
self.actions = actions or []
super().__init__(None, parent)
def _setup_ui(self):
layout = QHBoxLayout(self)
layout.setContentsMargins(12, 12, 12, 12)
layout.setSpacing(8)
for action in self.actions:
btn = QPushButton()
# Use icon if name matches an icon, otherwise use text
icon_name = action.get('icon', 'target')
icon_pixmap = self.icon_manager.get_pixmap(icon_name, size=18)
btn.setIcon(QIcon(icon_pixmap))
btn.setIconSize(QSize(18, 18))
btn.setFixedSize(36, 36)
btn.setStyleSheet("""
QPushButton {
background-color: rgba(255, 255, 255, 15);
border: 1px solid rgba(255, 255, 255, 30);
border-radius: 8px;
}
QPushButton:hover {
background-color: rgba(255, 255, 255, 30);
}
""")
btn.setToolTip(action['name'])
btn.clicked.connect(action['callback'])
layout.addWidget(btn)
layout.addStretch()
class Dashboard(QWidget):
"""Main dashboard with customizable widgets."""
widget_added = pyqtSignal(object)
widget_removed = pyqtSignal(object)
def __init__(self, plugin_manager=None, parent=None):
super().__init__(parent)
self.plugin_manager = plugin_manager
self.widgets = []
self.dragged_widget = None
self._setup_ui()
def _setup_ui(self):
"""Setup dashboard UI."""
self.setStyleSheet("background: transparent;")
layout = QVBoxLayout(self)
layout.setContentsMargins(20, 20, 20, 20)
layout.setSpacing(15)
# Header
c = get_color
header_layout = QHBoxLayout()
# Icon
header_icon = QLabel()
icon_pixmap = self.icon_manager.get_pixmap('zap', size=24)
header_icon.setPixmap(icon_pixmap)
header_icon.setFixedSize(24, 24)
header_layout.addWidget(header_icon)
header = QLabel("Dashboard")
header.setStyleSheet(f"""
color: {c('text_primary')};
font-size: 20px;
font-weight: bold;
""")
header_layout.addWidget(header)
header_layout.addStretch()
layout.addLayout(header_layout)
# Subtitle
subtitle = QLabel("Customize your EU-Utility experience")
subtitle.setStyleSheet("color: rgba(255, 255, 255, 150); font-size: 12px;")
layout.addWidget(subtitle)
# Widget grid container
scroll = QScrollArea()
scroll.setWidgetResizable(True)
scroll.setStyleSheet("""
QScrollArea {
background: transparent;
border: none;
}
QScrollBar:vertical {
background: rgba(0, 0, 0, 50);
width: 8px;
border-radius: 4px;
}
QScrollBar::handle:vertical {
background: rgba(255, 255, 255, 30);
border-radius: 4px;
}
""")
self.grid_widget = QWidget()
self.grid_layout = QGridLayout(self.grid_widget)
self.grid_layout.setSpacing(12)
self.grid_layout.setContentsMargins(0, 0, 0, 0)
scroll.setWidget(self.grid_widget)
layout.addWidget(scroll)
# Add default widgets
self._add_default_widgets()
def _add_default_widgets(self):
"""Add default widgets to dashboard."""
# Quick actions
actions = [
{'icon': 'search', 'name': 'Search', 'callback': lambda: None},
{'icon': 'camera', 'name': 'Scan', 'callback': lambda: None},
{'icon': 'bar-chart', 'name': 'Skills', 'callback': lambda: None},
{'icon': 'music', 'name': 'Music', 'callback': lambda: None},
]
self.add_widget(QuickActionsWidget(actions))
# Spotify widget removed - should be in plugins/spotify_controller instead
# self.add_widget(SpotifyWidget())
# PED tracker
self.add_widget(PEDTrackerWidget())
# Skill progress
self.add_widget(SkillProgressWidget())
def add_widget(self, widget):
"""Add a widget to the dashboard."""
row = self.grid_layout.rowCount()
col = 0
# Find next available position
while self.grid_layout.itemAtPosition(row, col):
col += 1
if col >= 2: # Max 2 columns
col = 0
row += 1
self.grid_layout.addWidget(widget, row, col,
widget.size[1], widget.size[0])
self.widgets.append(widget)
self.widget_added.emit(widget)
def remove_widget(self, widget):
"""Remove a widget from the dashboard."""
self.grid_layout.removeWidget(widget)
widget.deleteLater()
self.widgets.remove(widget)
self.widget_removed.emit(widget)
def start_widget_drag(self, widget):
"""Start dragging a widget."""
self.dragged_widget = widget
def get_widget_positions(self):
"""Get current widget positions for saving."""
positions = []
for widget in self.widgets:
idx = self.grid_layout.indexOf(widget)
if idx >= 0:
row, col, rowSpan, colSpan = self.grid_layout.getItemPosition(idx)
positions.append({
'type': widget.__class__.__name__,
'row': row,
'col': col,
'size': widget.size
})
return positions