598 lines
19 KiB
Python
598 lines
19 KiB
Python
"""
|
||
EU-Utility - Classy Dashboard UI
|
||
================================
|
||
|
||
A refined, elegant dashboard interface for second monitor use.
|
||
Features a clean layout with a proper Dashboard for widgets,
|
||
removing the sidebar for a more modern feel.
|
||
|
||
Design Philosophy:
|
||
- Clean, minimalist aesthetic
|
||
- Premium dark theme with subtle accents
|
||
- Glassmorphism effects
|
||
- Smooth animations
|
||
- Widget-focused dashboard
|
||
"""
|
||
|
||
from PyQt6.QtWidgets import (
|
||
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||
QStackedWidget, QLabel, QPushButton, QFrame,
|
||
QScrollArea, QGridLayout, QSizePolicy, QSpacerItem
|
||
)
|
||
from PyQt6.QtCore import Qt, QTimer, pyqtSignal, QSize, QPropertyAnimation, QEasingCurve
|
||
from PyQt6.QtGui import QColor, QPainter, QLinearGradient, QFont, QIcon
|
||
|
||
from core.eu_styles import get_all_colors, EU_TYPOGRAPHY, EU_SIZES
|
||
|
||
|
||
class GlassCard(QFrame):
|
||
"""Glassmorphism card widget."""
|
||
|
||
def __init__(self, parent=None):
|
||
super().__init__(parent)
|
||
self.setObjectName("glassCard")
|
||
self._setup_style()
|
||
|
||
def _setup_style(self):
|
||
c = get_all_colors()
|
||
self.setStyleSheet(f"""
|
||
QFrame#glassCard {{
|
||
background: rgba(45, 55, 72, 0.6);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
border-radius: 16px;
|
||
}}
|
||
QFrame#glassCard:hover {{
|
||
background: rgba(45, 55, 72, 0.75);
|
||
border: 1px solid rgba(255, 140, 66, 0.3);
|
||
}}
|
||
""")
|
||
|
||
|
||
class ElegantTabButton(QPushButton):
|
||
"""Elegant tab button with smooth hover effects."""
|
||
|
||
clicked_tab = pyqtSignal(str)
|
||
|
||
def __init__(self, text: str, icon: str = None, tab_id: str = None, parent=None):
|
||
super().__init__(text, parent)
|
||
self.tab_id = tab_id or text.lower()
|
||
self._active = False
|
||
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||
self.setFixedHeight(44)
|
||
self.clicked.connect(lambda: self.clicked_tab.emit(self.tab_id))
|
||
self._update_style()
|
||
|
||
def set_active(self, active: bool):
|
||
self._active = active
|
||
self._update_style()
|
||
|
||
def _update_style(self):
|
||
c = get_all_colors()
|
||
if self._active:
|
||
self.setStyleSheet(f"""
|
||
QPushButton {{
|
||
background: transparent;
|
||
color: {c['accent_orange']};
|
||
border: none;
|
||
border-bottom: 2px solid {c['accent_orange']};
|
||
padding: 0 24px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
}}
|
||
""")
|
||
else:
|
||
self.setStyleSheet(f"""
|
||
QPushButton {{
|
||
background: transparent;
|
||
color: {c['text_secondary']};
|
||
border: none;
|
||
border-bottom: 2px solid transparent;
|
||
padding: 0 24px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
}}
|
||
QPushButton:hover {{
|
||
color: {c['text_primary']};
|
||
}}
|
||
""")
|
||
|
||
|
||
class DashboardWidget(QFrame):
|
||
"""A widget container for the Dashboard grid."""
|
||
|
||
def __init__(self, title: str, content_widget=None, parent=None):
|
||
super().__init__(parent)
|
||
self.title = title
|
||
self._setup_ui(content_widget)
|
||
|
||
def _setup_ui(self, content_widget):
|
||
c = get_all_colors()
|
||
|
||
self.setStyleSheet(f"""
|
||
QFrame {{
|
||
background: rgba(35, 41, 54, 0.8);
|
||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||
border-radius: 20px;
|
||
}}
|
||
""")
|
||
|
||
layout = QVBoxLayout(self)
|
||
layout.setContentsMargins(20, 20, 20, 20)
|
||
layout.setSpacing(16)
|
||
|
||
# Header with title
|
||
header = QLabel(self.title)
|
||
header.setStyleSheet(f"""
|
||
color: {c['text_primary']};
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
""")
|
||
layout.addWidget(header)
|
||
|
||
# Separator line
|
||
separator = QFrame()
|
||
separator.setFixedHeight(1)
|
||
separator.setStyleSheet(f"background: rgba(255, 255, 255, 0.08);")
|
||
layout.addWidget(separator)
|
||
|
||
# Content
|
||
if content_widget:
|
||
layout.addWidget(content_widget, 1)
|
||
|
||
self.setMinimumSize(280, 200)
|
||
|
||
|
||
class ClassyDashboardWindow(QMainWindow):
|
||
"""
|
||
Refined EU-Utility main window with elegant Dashboard.
|
||
|
||
Features:
|
||
- Clean, sidebar-free layout
|
||
- Dashboard tab for widgets (second monitor friendly)
|
||
- Elegant glassmorphism design
|
||
- Smooth animations and transitions
|
||
"""
|
||
|
||
def __init__(self, plugin_manager, parent=None):
|
||
super().__init__(parent)
|
||
self.plugin_manager = plugin_manager
|
||
self.current_tab = "dashboard"
|
||
self.tab_buttons = {}
|
||
|
||
self._setup_window()
|
||
self._setup_central_widget()
|
||
self._create_header()
|
||
self._create_tabs()
|
||
self._create_content_area()
|
||
|
||
# Show Dashboard by default
|
||
self._switch_tab("dashboard")
|
||
|
||
def _setup_window(self):
|
||
"""Setup window properties."""
|
||
self.setWindowTitle("EU-Utility")
|
||
self.setMinimumSize(1200, 800)
|
||
self.resize(1400, 900)
|
||
|
||
# Center window
|
||
screen = self.screen().geometry()
|
||
self.move(
|
||
(screen.width() - self.width()) // 2,
|
||
(screen.height() - self.height()) // 2
|
||
)
|
||
|
||
# Dark, elegant background
|
||
c = get_all_colors()
|
||
self.setStyleSheet(f"""
|
||
QMainWindow {{
|
||
background: #0f1419;
|
||
}}
|
||
""")
|
||
|
||
def _setup_central_widget(self):
|
||
"""Create central widget with gradient background."""
|
||
self.central = QWidget()
|
||
self.setCentralWidget(self.central)
|
||
|
||
layout = QVBoxLayout(self.central)
|
||
layout.setContentsMargins(0, 0, 0, 0)
|
||
layout.setSpacing(0)
|
||
|
||
def _create_header(self):
|
||
"""Create elegant header with logo and controls."""
|
||
c = get_all_colors()
|
||
|
||
header = QFrame()
|
||
header.setFixedHeight(72)
|
||
header.setStyleSheet(f"""
|
||
QFrame {{
|
||
background: rgba(15, 20, 25, 0.95);
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||
}}
|
||
""")
|
||
|
||
layout = QHBoxLayout(header)
|
||
layout.setContentsMargins(32, 0, 32, 0)
|
||
layout.setSpacing(24)
|
||
|
||
# Logo
|
||
logo = QLabel("◆ EU-Utility")
|
||
logo.setStyleSheet(f"""
|
||
color: {c['accent_orange']};
|
||
font-size: 22px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.5px;
|
||
""")
|
||
layout.addWidget(logo)
|
||
|
||
# Spacer
|
||
layout.addStretch()
|
||
|
||
# Window controls
|
||
minimize_btn = QPushButton("−")
|
||
minimize_btn.setFixedSize(40, 32)
|
||
minimize_btn.setStyleSheet(f"""
|
||
QPushButton {{
|
||
background: transparent;
|
||
color: {c['text_secondary']};
|
||
border: none;
|
||
font-size: 18px;
|
||
border-radius: 6px;
|
||
}}
|
||
QPushButton:hover {{
|
||
background: rgba(255, 255, 255, 0.1);
|
||
color: {c['text_primary']};
|
||
}}
|
||
""")
|
||
minimize_btn.clicked.connect(self.showMinimized)
|
||
|
||
close_btn = QPushButton("×")
|
||
close_btn.setFixedSize(40, 32)
|
||
close_btn.setStyleSheet(f"""
|
||
QPushButton {{
|
||
background: transparent;
|
||
color: {c['text_secondary']};
|
||
border: none;
|
||
font-size: 20px;
|
||
border-radius: 6px;
|
||
}}
|
||
QPushButton:hover {{
|
||
background: #e53e3e;
|
||
color: white;
|
||
}}
|
||
""")
|
||
close_btn.clicked.connect(self.close)
|
||
|
||
layout.addWidget(minimize_btn)
|
||
layout.addWidget(close_btn)
|
||
|
||
self.central.layout().addWidget(header)
|
||
|
||
def _create_tabs(self):
|
||
"""Create elegant tab bar."""
|
||
c = get_all_colors()
|
||
|
||
tab_bar = QFrame()
|
||
tab_bar.setFixedHeight(64)
|
||
tab_bar.setStyleSheet(f"""
|
||
QFrame {{
|
||
background: rgba(15, 20, 25, 0.9);
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
||
}}
|
||
""")
|
||
|
||
layout = QHBoxLayout(tab_bar)
|
||
layout.setContentsMargins(32, 0, 32, 0)
|
||
layout.setSpacing(8)
|
||
layout.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
|
||
|
||
# Tab definitions
|
||
tabs = [
|
||
("Dashboard", "dashboard"),
|
||
("Plugins", "plugins"),
|
||
("Widgets", "widgets"),
|
||
("Settings", "settings"),
|
||
]
|
||
|
||
for text, tab_id in tabs:
|
||
btn = ElegantTabButton(text, tab_id=tab_id)
|
||
btn.clicked_tab.connect(self._switch_tab)
|
||
self.tab_buttons[tab_id] = btn
|
||
layout.addWidget(btn)
|
||
|
||
layout.addStretch()
|
||
|
||
self.central.layout().addWidget(tab_bar)
|
||
|
||
def _create_content_area(self):
|
||
"""Create main content stack."""
|
||
self.content_stack = QStackedWidget()
|
||
self.content_stack.setStyleSheet("background: transparent;")
|
||
|
||
# Create all tabs
|
||
self.dashboard_tab = self._create_dashboard_tab()
|
||
self.plugins_tab = self._create_plugins_tab()
|
||
self.widgets_tab = self._create_widgets_tab()
|
||
self.settings_tab = self._create_settings_tab()
|
||
|
||
self.content_stack.addWidget(self.dashboard_tab)
|
||
self.content_stack.addWidget(self.plugins_tab)
|
||
self.content_stack.addWidget(self.widgets_tab)
|
||
self.content_stack.addWidget(self.settings_tab)
|
||
|
||
self.central.layout().addWidget(self.content_stack, 1)
|
||
|
||
def _create_dashboard_tab(self) -> QWidget:
|
||
"""Create the Dashboard tab - a grid for widgets."""
|
||
scroll = QScrollArea()
|
||
scroll.setWidgetResizable(True)
|
||
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||
scroll.setStyleSheet("""
|
||
QScrollArea { background: transparent; border: none; }
|
||
QScrollBar:vertical {
|
||
background: transparent;
|
||
width: 8px;
|
||
margin: 0;
|
||
}
|
||
QScrollBar::handle:vertical {
|
||
background: rgba(255, 255, 255, 0.1);
|
||
border-radius: 4px;
|
||
min-height: 40px;
|
||
}
|
||
QScrollBar::handle:vertical:hover {
|
||
background: rgba(255, 255, 255, 0.2);
|
||
}
|
||
""")
|
||
|
||
container = QWidget()
|
||
container.setStyleSheet("background: transparent;")
|
||
|
||
layout = QVBoxLayout(container)
|
||
layout.setContentsMargins(32, 32, 32, 32)
|
||
layout.setSpacing(24)
|
||
|
||
# Welcome header
|
||
c = get_all_colors()
|
||
welcome = QLabel("Dashboard")
|
||
welcome.setStyleSheet(f"""
|
||
color: {c['text_primary']};
|
||
font-size: 32px;
|
||
font-weight: 700;
|
||
""")
|
||
layout.addWidget(welcome)
|
||
|
||
subtitle = QLabel("Your personal command center. Add widgets to track what's important.")
|
||
subtitle.setStyleSheet(f"""
|
||
color: {c['text_secondary']};
|
||
font-size: 14px;
|
||
margin-bottom: 16px;
|
||
""")
|
||
layout.addWidget(subtitle)
|
||
|
||
# Widget grid
|
||
grid = QGridLayout()
|
||
grid.setSpacing(20)
|
||
grid.setColumnStretch(0, 1)
|
||
grid.setColumnStretch(1, 1)
|
||
grid.setColumnStretch(2, 1)
|
||
|
||
# Add some default widgets
|
||
widgets = [
|
||
("System Status", self._create_system_widget()),
|
||
("Quick Actions", self._create_actions_widget()),
|
||
("Recent Activity", self._create_activity_widget()),
|
||
]
|
||
|
||
for i, (title, content) in enumerate(widgets):
|
||
widget = DashboardWidget(title, content)
|
||
grid.addWidget(widget, i // 3, i % 3)
|
||
|
||
layout.addLayout(grid)
|
||
layout.addStretch()
|
||
|
||
scroll.setWidget(container)
|
||
return scroll
|
||
|
||
def _create_system_widget(self) -> QWidget:
|
||
"""Create system status widget content."""
|
||
c = get_all_colors()
|
||
widget = QWidget()
|
||
layout = QVBoxLayout(widget)
|
||
layout.setSpacing(12)
|
||
|
||
status_items = [
|
||
("●", "Log Reader", "Active", "#4ecca3"),
|
||
("●", "OCR Service", "Ready", "#4ecca3"),
|
||
("●", "Event Bus", "Running", "#4ecca3"),
|
||
("○", "Nexus API", "Idle", "#ffd93d"),
|
||
]
|
||
|
||
for icon, name, status, color in status_items:
|
||
row = QHBoxLayout()
|
||
|
||
icon_label = QLabel(icon)
|
||
icon_label.setStyleSheet(f"color: {color}; font-size: 12px;")
|
||
row.addWidget(icon_label)
|
||
|
||
name_label = QLabel(name)
|
||
name_label.setStyleSheet(f"color: {c['text_primary']}; font-size: 13px;")
|
||
row.addWidget(name_label)
|
||
|
||
row.addStretch()
|
||
|
||
status_label = QLabel(status)
|
||
status_label.setStyleSheet(f"color: {c['text_secondary']}; font-size: 12px;")
|
||
row.addWidget(status_label)
|
||
|
||
layout.addLayout(row)
|
||
|
||
return widget
|
||
|
||
def _create_actions_widget(self) -> QWidget:
|
||
"""Create quick actions widget content."""
|
||
c = get_all_colors()
|
||
widget = QWidget()
|
||
layout = QVBoxLayout(widget)
|
||
layout.setSpacing(10)
|
||
|
||
actions = [
|
||
("Scan Skills", "📷"),
|
||
("Check Loot", "📦"),
|
||
("Search Nexus", "🔍"),
|
||
("Settings", "⚙️"),
|
||
]
|
||
|
||
for text, emoji in actions:
|
||
btn = QPushButton(f"{emoji} {text}")
|
||
btn.setFixedHeight(40)
|
||
btn.setStyleSheet(f"""
|
||
QPushButton {{
|
||
background: rgba(255, 255, 255, 0.05);
|
||
color: {c['text_primary']};
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
border-radius: 10px;
|
||
font-size: 13px;
|
||
text-align: left;
|
||
padding-left: 16px;
|
||
}}
|
||
QPushButton:hover {{
|
||
background: rgba(255, 140, 66, 0.15);
|
||
border: 1px solid rgba(255, 140, 66, 0.3);
|
||
}}
|
||
""")
|
||
layout.addWidget(btn)
|
||
|
||
return widget
|
||
|
||
def _create_activity_widget(self) -> QWidget:
|
||
"""Create recent activity widget content."""
|
||
c = get_all_colors()
|
||
widget = QWidget()
|
||
layout = QVBoxLayout(widget)
|
||
layout.setSpacing(10)
|
||
|
||
activities = [
|
||
("Plugin updated", "Clock Widget v1.0.1", "2m ago"),
|
||
("Scan completed", "Found 12 skills", "15m ago"),
|
||
("Settings changed", "Theme: Dark", "1h ago"),
|
||
]
|
||
|
||
for title, detail, time in activities:
|
||
item = QFrame()
|
||
item.setStyleSheet("background: transparent;")
|
||
row = QVBoxLayout(item)
|
||
row.setSpacing(2)
|
||
|
||
top = QHBoxLayout()
|
||
title_label = QLabel(title)
|
||
title_label.setStyleSheet(f"color: {c['text_primary']}; font-size: 13px; font-weight: 500;")
|
||
top.addWidget(title_label)
|
||
|
||
top.addStretch()
|
||
|
||
time_label = QLabel(time)
|
||
time_label.setStyleSheet(f"color: {c['text_muted']}; font-size: 11px;")
|
||
top.addWidget(time_label)
|
||
|
||
row.addLayout(top)
|
||
|
||
detail_label = QLabel(detail)
|
||
detail_label.setStyleSheet(f"color: {c['text_secondary']}; font-size: 12px;")
|
||
row.addWidget(detail_label)
|
||
|
||
layout.addWidget(item)
|
||
|
||
layout.addStretch()
|
||
return widget
|
||
|
||
def _create_plugins_tab(self) -> QWidget:
|
||
"""Create Plugins tab content."""
|
||
widget = QWidget()
|
||
layout = QVBoxLayout(widget)
|
||
layout.setContentsMargins(32, 32, 32, 32)
|
||
|
||
c = get_all_colors()
|
||
title = QLabel("Plugins")
|
||
title.setStyleSheet(f"""
|
||
color: {c['text_primary']};
|
||
font-size: 32px;
|
||
font-weight: 700;
|
||
""")
|
||
layout.addWidget(title)
|
||
|
||
# Plugin list placeholder
|
||
placeholder = QLabel("Plugin management interface - to be implemented")
|
||
placeholder.setStyleSheet(f"color: {c['text_secondary']}; padding: 40px;")
|
||
layout.addWidget(placeholder)
|
||
layout.addStretch()
|
||
|
||
return widget
|
||
|
||
def _create_widgets_tab(self) -> QWidget:
|
||
"""Create Widgets tab content."""
|
||
widget = QWidget()
|
||
layout = QVBoxLayout(widget)
|
||
layout.setContentsMargins(32, 32, 32, 32)
|
||
|
||
c = get_all_colors()
|
||
title = QLabel("Widgets")
|
||
title.setStyleSheet(f"""
|
||
color: {c['text_primary']};
|
||
font-size: 32px;
|
||
font-weight: 700;
|
||
""")
|
||
layout.addWidget(title)
|
||
|
||
placeholder = QLabel("Widget gallery - to be implemented")
|
||
placeholder.setStyleSheet(f"color: {c['text_secondary']}; padding: 40px;")
|
||
layout.addWidget(placeholder)
|
||
layout.addStretch()
|
||
|
||
return widget
|
||
|
||
def _create_settings_tab(self) -> QWidget:
|
||
"""Create Settings tab content."""
|
||
widget = QWidget()
|
||
layout = QVBoxLayout(widget)
|
||
layout.setContentsMargins(32, 32, 32, 32)
|
||
|
||
c = get_all_colors()
|
||
title = QLabel("Settings")
|
||
title.setStyleSheet(f"""
|
||
color: {c['text_primary']};
|
||
font-size: 32px;
|
||
font-weight: 700;
|
||
""")
|
||
layout.addWidget(title)
|
||
|
||
placeholder = QLabel("Settings interface - to be implemented")
|
||
placeholder.setStyleSheet(f"color: {c['text_secondary']}; padding: 40px;")
|
||
layout.addWidget(placeholder)
|
||
layout.addStretch()
|
||
|
||
return widget
|
||
|
||
def _switch_tab(self, tab_id: str):
|
||
"""Switch to the specified tab."""
|
||
self.current_tab = tab_id
|
||
|
||
# Update button states
|
||
for btn_id, btn in self.tab_buttons.items():
|
||
btn.set_active(btn_id == tab_id)
|
||
|
||
# Update content stack
|
||
tab_map = {
|
||
"dashboard": 0,
|
||
"plugins": 1,
|
||
"widgets": 2,
|
||
"settings": 3,
|
||
}
|
||
if tab_id in tab_map:
|
||
self.content_stack.setCurrentIndex(tab_map[tab_id])
|
||
|
||
|
||
# Factory function for creating the main window
|
||
def create_classy_dashboard(plugin_manager) -> ClassyDashboardWindow:
|
||
"""Create and return the classy dashboard window."""
|
||
return ClassyDashboardWindow(plugin_manager)
|