1358 lines
48 KiB
Python
1358 lines
48 KiB
Python
"""
|
|
EU-Utility - Enhanced Overlay Window
|
|
|
|
Features:
|
|
- EU game aesthetic matching
|
|
- Responsive layouts
|
|
- Dark/light theme support
|
|
- Accessibility improvements
|
|
- Smooth animations and transitions
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Optional, List
|
|
|
|
try:
|
|
from PyQt6.QtWidgets import (
|
|
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
|
QLabel, QPushButton, QStackedWidget, QSystemTrayIcon,
|
|
QMenu, QApplication, QFrame, QListWidget, QListWidgetItem,
|
|
QButtonGroup, QSplitter, QSizePolicy, QCheckBox, QTabWidget,
|
|
QScrollArea
|
|
)
|
|
from PyQt6.QtCore import Qt, QTimer, pyqtSignal, QSize, QPropertyAnimation, QEasingCurve, QParallelAnimationGroup
|
|
from PyQt6.QtGui import QAction, QIcon, QColor, QFont, QKeySequence, QShortcut
|
|
PYQT6_AVAILABLE = True
|
|
except ImportError:
|
|
PYQT6_AVAILABLE = False
|
|
print("PyQt6 not available. Install with: pip install PyQt6")
|
|
|
|
from core.eu_styles import (
|
|
EUTheme, get_color, get_all_colors, get_global_stylesheet,
|
|
get_button_style, get_input_style, get_table_style, get_card_style,
|
|
EU_TYPOGRAPHY, EU_SPACING, EU_SIZES, AnimationHelper, ResponsiveHelper,
|
|
AccessibilityHelper
|
|
)
|
|
from core.icon_manager import get_icon_manager, get_plugin_icon_name
|
|
|
|
|
|
class AnimatedButton(QPushButton):
|
|
"""Button with hover animation."""
|
|
|
|
def __init__(self, text: str = "", parent=None):
|
|
super().__init__(text, parent)
|
|
self._setup_animation()
|
|
|
|
def _setup_animation(self):
|
|
"""Setup scale animation on hover."""
|
|
self._anim = QPropertyAnimation(self, b"geometry")
|
|
self._anim.setDuration(150)
|
|
self._anim.setEasingCurve(QEasingCurve.Type.OutQuad)
|
|
|
|
def enterEvent(self, event):
|
|
"""Handle mouse enter with animation."""
|
|
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
|
super().enterEvent(event)
|
|
|
|
def leaveEvent(self, event):
|
|
"""Handle mouse leave."""
|
|
self.setCursor(Qt.CursorShape.ArrowCursor)
|
|
super().leaveEvent(event)
|
|
|
|
|
|
class SidebarButton(QFrame):
|
|
"""Custom sidebar button with EU styling."""
|
|
|
|
clicked = pyqtSignal()
|
|
|
|
def __init__(self, name: str, icon_name: str, icon_manager, parent=None):
|
|
super().__init__(parent)
|
|
self.name = name
|
|
self.icon_name = icon_name
|
|
self.icon_manager = icon_manager
|
|
self.is_selected = False
|
|
|
|
self._setup_ui()
|
|
self._setup_animations()
|
|
|
|
def _setup_ui(self):
|
|
"""Setup button UI."""
|
|
c = get_all_colors()
|
|
|
|
self.setFixedHeight(44)
|
|
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
|
|
|
layout = QHBoxLayout(self)
|
|
layout.setContentsMargins(12, 0, 12, 0)
|
|
layout.setSpacing(12)
|
|
|
|
# Icon
|
|
self.icon_label = QLabel()
|
|
icon_pixmap = self.icon_manager.get_pixmap(self.icon_name, size=20)
|
|
self.icon_label.setPixmap(icon_pixmap)
|
|
self.icon_label.setFixedSize(20, 20)
|
|
layout.addWidget(self.icon_label)
|
|
|
|
# Text
|
|
self.text_label = QLabel(self.name)
|
|
self.text_label.setStyleSheet(f"""
|
|
color: {c['text_secondary']};
|
|
font-size: {EU_TYPOGRAPHY['size_sm']};
|
|
font-weight: {EU_TYPOGRAPHY['weight_medium']};
|
|
""")
|
|
layout.addWidget(self.text_label)
|
|
layout.addStretch()
|
|
|
|
self._update_style()
|
|
|
|
def _setup_animations(self):
|
|
"""Setup hover animations."""
|
|
self._hover_anim = QPropertyAnimation(self, b"minimumHeight")
|
|
self._hover_anim.setDuration(150)
|
|
self._hover_anim.setEasingCurve(QEasingCurve.Type.OutQuad)
|
|
|
|
def _update_style(self):
|
|
"""Update button style based on state."""
|
|
c = get_all_colors()
|
|
|
|
if self.is_selected:
|
|
self.setStyleSheet(f"""
|
|
SidebarButton {{
|
|
background-color: {c['bg_selected']};
|
|
border-left: 3px solid {c['accent_orange']};
|
|
border-radius: 0px;
|
|
}}
|
|
""")
|
|
self.text_label.setStyleSheet(f"""
|
|
color: {c['text_primary']};
|
|
font-size: {EU_TYPOGRAPHY['size_sm']};
|
|
font-weight: {EU_TYPOGRAPHY['weight_semibold']};
|
|
""")
|
|
else:
|
|
self.setStyleSheet(f"""
|
|
SidebarButton {{
|
|
background-color: transparent;
|
|
border-left: 3px solid transparent;
|
|
border-radius: 0px;
|
|
}}
|
|
SidebarButton:hover {{
|
|
background-color: {c['bg_hover']};
|
|
}}
|
|
""")
|
|
self.text_label.setStyleSheet(f"""
|
|
color: {c['text_secondary']};
|
|
font-size: {EU_TYPOGRAPHY['size_sm']};
|
|
font-weight: {EU_TYPOGRAPHY['weight_medium']};
|
|
""")
|
|
|
|
def set_selected(self, selected: bool):
|
|
"""Set selected state."""
|
|
self.is_selected = selected
|
|
self._update_style()
|
|
|
|
def mousePressEvent(self, event):
|
|
"""Handle click."""
|
|
if event.button() == Qt.MouseButton.LeftButton:
|
|
self.clicked.emit()
|
|
super().mousePressEvent(event)
|
|
|
|
def enterEvent(self, event):
|
|
"""Handle hover enter."""
|
|
if not self.is_selected:
|
|
c = get_all_colors()
|
|
self.text_label.setStyleSheet(f"""
|
|
color: {c['text_primary']};
|
|
font-size: {EU_TYPOGRAPHY['size_sm']};
|
|
font-weight: {EU_TYPOGRAPHY['weight_medium']};
|
|
""")
|
|
super().enterEvent(event)
|
|
|
|
def leaveEvent(self, event):
|
|
"""Handle hover leave."""
|
|
if not self.is_selected:
|
|
c = get_all_colors()
|
|
self.text_label.setStyleSheet(f"""
|
|
color: {c['text_secondary']};
|
|
font-size: {EU_TYPOGRAPHY['size_sm']};
|
|
font-weight: {EU_TYPOGRAPHY['weight_medium']};
|
|
""")
|
|
super().leaveEvent(event)
|
|
|
|
|
|
class OverlayWindow(QMainWindow):
|
|
"""
|
|
Enhanced EU-styled overlay window with responsive design,
|
|
animations, and accessibility features.
|
|
"""
|
|
|
|
visibility_changed = pyqtSignal(bool)
|
|
theme_changed = pyqtSignal(str)
|
|
|
|
def __init__(self, plugin_manager=None):
|
|
super().__init__()
|
|
|
|
if not PYQT6_AVAILABLE:
|
|
raise ImportError("PyQt6 is required")
|
|
|
|
self.plugin_manager = plugin_manager
|
|
self.is_visible = False
|
|
self.sidebar_buttons: List[SidebarButton] = []
|
|
self.current_plugin_index = 0
|
|
self.icon_manager = get_icon_manager()
|
|
|
|
# Animation tracking
|
|
self._fade_anim = None
|
|
self._slide_anim = None
|
|
|
|
self._setup_window()
|
|
self._setup_ui()
|
|
self._setup_tray()
|
|
self._setup_shortcuts()
|
|
self._setup_animations()
|
|
|
|
# Apply global stylesheet
|
|
self.setStyleSheet(get_global_stylesheet())
|
|
|
|
self.hide_overlay()
|
|
|
|
def _setup_window(self):
|
|
"""Configure window properties."""
|
|
c = get_all_colors()
|
|
|
|
self.setWindowTitle("EU-Utility")
|
|
self.setWindowFlags(
|
|
Qt.WindowType.Window |
|
|
Qt.WindowType.WindowStaysOnTopHint
|
|
)
|
|
|
|
self.setMinimumSize(700, 500)
|
|
self.resize(900, 650)
|
|
self._center_window()
|
|
|
|
# Set window background
|
|
self.setStyleSheet(f"""
|
|
QMainWindow {{
|
|
background-color: {c['bg_primary']};
|
|
}}
|
|
""")
|
|
|
|
def _center_window(self):
|
|
"""Center window on screen."""
|
|
screen = QApplication.primaryScreen().geometry()
|
|
x = (screen.width() - self.width()) // 2
|
|
y = (screen.height() - self.height()) // 3
|
|
self.move(x, y)
|
|
|
|
def _setup_ui(self):
|
|
"""Setup the main UI with responsive layout."""
|
|
c = get_all_colors()
|
|
|
|
central = QWidget()
|
|
self.setCentralWidget(central)
|
|
|
|
# Main layout
|
|
layout = QVBoxLayout(central)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.setSpacing(0)
|
|
|
|
# Header
|
|
header = self._create_header()
|
|
layout.addWidget(header)
|
|
|
|
# Content splitter (sidebar + main content)
|
|
self.splitter = QSplitter(Qt.Orientation.Horizontal)
|
|
self.splitter.setHandleWidth(1)
|
|
self.splitter.setStyleSheet(f"""
|
|
QSplitter::handle {{
|
|
background-color: {c['border_default']};
|
|
}}
|
|
""")
|
|
|
|
# Sidebar
|
|
self.sidebar = self._create_sidebar()
|
|
self.splitter.addWidget(self.sidebar)
|
|
|
|
# Main content area
|
|
self.content_area = self._create_content_area()
|
|
self.splitter.addWidget(self.content_area)
|
|
|
|
# Set splitter sizes (sidebar takes ~220px, content takes rest)
|
|
self.splitter.setSizes([220, 680])
|
|
|
|
layout.addWidget(self.splitter, 1)
|
|
|
|
# Load plugins
|
|
if self.plugin_manager:
|
|
self._load_plugins()
|
|
|
|
def _create_header(self) -> QWidget:
|
|
"""Create the header bar."""
|
|
c = get_all_colors()
|
|
|
|
header = QFrame()
|
|
header.setObjectName("header")
|
|
header.setFixedHeight(56)
|
|
header.setStyleSheet(f"""
|
|
QFrame#header {{
|
|
background-color: {c['bg_secondary']};
|
|
border-bottom: 1px solid {c['border_default']};
|
|
}}
|
|
""")
|
|
|
|
layout = QHBoxLayout(header)
|
|
layout.setContentsMargins(16, 0, 16, 0)
|
|
layout.setSpacing(12)
|
|
|
|
# App icon and title
|
|
app_icon = QLabel()
|
|
icon_pixmap = self.icon_manager.get_pixmap("target", size=24)
|
|
app_icon.setPixmap(icon_pixmap)
|
|
app_icon.setFixedSize(24, 24)
|
|
layout.addWidget(app_icon)
|
|
|
|
title = QLabel("EU-Utility")
|
|
title.setStyleSheet(f"""
|
|
color: {c['text_primary']};
|
|
font-size: {EU_TYPOGRAPHY['size_lg']};
|
|
font-weight: {EU_TYPOGRAPHY['weight_bold']};
|
|
""")
|
|
layout.addWidget(title)
|
|
|
|
# Version badge
|
|
version = QLabel("v2.0")
|
|
version.setStyleSheet(f"""
|
|
color: {c['text_muted']};
|
|
font-size: {EU_TYPOGRAPHY['size_xs']};
|
|
background-color: {c['bg_tertiary']};
|
|
padding: 2px 8px;
|
|
border-radius: {EU_SIZES['radius_sm']};
|
|
""")
|
|
layout.addWidget(version)
|
|
|
|
layout.addStretch()
|
|
|
|
# Theme toggle
|
|
theme_icon = 'sun' if EUTheme.is_dark() else 'moon'
|
|
self.theme_btn = QPushButton()
|
|
theme_pixmap = self.icon_manager.get_pixmap(theme_icon, size=20)
|
|
self.theme_btn.setIcon(QIcon(theme_pixmap))
|
|
self.theme_btn.setIconSize(QSize(20, 20))
|
|
self.theme_btn.setFixedSize(36, 36)
|
|
self.theme_btn.setStyleSheet(get_button_style('ghost', 'sm'))
|
|
self.theme_btn.setToolTip("Toggle theme (Ctrl+T)")
|
|
self.theme_btn.clicked.connect(self._toggle_theme)
|
|
layout.addWidget(self.theme_btn)
|
|
|
|
# Settings button
|
|
settings_btn = AnimatedButton("Settings")
|
|
settings_btn.setStyleSheet(get_button_style('secondary', 'sm'))
|
|
settings_btn.setAccessibleName("Settings")
|
|
settings_btn.setAccessibleDescription("Open application settings")
|
|
settings_btn.clicked.connect(self._open_settings)
|
|
layout.addWidget(settings_btn)
|
|
|
|
# Close button
|
|
close_btn = QPushButton()
|
|
close_pixmap = self.icon_manager.get_pixmap('close', size=20)
|
|
close_btn.setIcon(QIcon(close_pixmap))
|
|
close_btn.setIconSize(QSize(20, 20))
|
|
close_btn.setFixedSize(36, 36)
|
|
close_btn.setStyleSheet(f"""
|
|
QPushButton {{
|
|
background-color: transparent;
|
|
color: {c['text_muted']};
|
|
font-size: 14px;
|
|
font-weight: {EU_TYPOGRAPHY['weight_bold']};
|
|
border: none;
|
|
border-radius: {EU_SIZES['radius_md']};
|
|
}}
|
|
QPushButton:hover {{
|
|
background-color: {c['accent_red']};
|
|
color: white;
|
|
}}
|
|
""")
|
|
close_btn.setAccessibleName("Close")
|
|
close_btn.setAccessibleDescription("Close the overlay window")
|
|
close_btn.clicked.connect(self.hide_overlay)
|
|
layout.addWidget(close_btn)
|
|
|
|
return header
|
|
|
|
def _create_sidebar(self) -> QWidget:
|
|
"""Create the sidebar with plugin navigation."""
|
|
c = get_all_colors()
|
|
|
|
sidebar = QWidget()
|
|
sidebar.setMinimumWidth(200)
|
|
sidebar.setMaximumWidth(280)
|
|
sidebar.setStyleSheet(f"""
|
|
QWidget {{
|
|
background-color: {c['bg_primary']};
|
|
}}
|
|
""")
|
|
|
|
layout = QVBoxLayout(sidebar)
|
|
layout.setContentsMargins(0, 16, 0, 16)
|
|
layout.setSpacing(4)
|
|
|
|
# Section label
|
|
plugins_label = QLabel("PLUGINS")
|
|
plugins_label.setStyleSheet(f"""
|
|
color: {c['text_muted']};
|
|
font-size: {EU_TYPOGRAPHY['size_xs']};
|
|
font-weight: {EU_TYPOGRAPHY['weight_bold']};
|
|
padding: 0 16px;
|
|
margin-bottom: 8px;
|
|
""")
|
|
layout.addWidget(plugins_label)
|
|
|
|
# Plugin buttons container
|
|
self.sidebar_buttons_container = QWidget()
|
|
self.sidebar_buttons_layout = QVBoxLayout(self.sidebar_buttons_container)
|
|
self.sidebar_buttons_layout.setContentsMargins(0, 0, 0, 0)
|
|
self.sidebar_buttons_layout.setSpacing(2)
|
|
self.sidebar_buttons_layout.addStretch()
|
|
|
|
layout.addWidget(self.sidebar_buttons_container)
|
|
|
|
# Add stretch at bottom
|
|
layout.addStretch()
|
|
|
|
# Keyboard hint
|
|
hint = QLabel("Ctrl+1-9 to switch")
|
|
hint.setStyleSheet(f"""
|
|
color: {c['text_muted']};
|
|
font-size: {EU_TYPOGRAPHY['size_xs']};
|
|
padding: 0 16px;
|
|
""")
|
|
hint.setAccessibleName("Keyboard shortcut hint")
|
|
layout.addWidget(hint)
|
|
|
|
return sidebar
|
|
|
|
def _create_content_area(self) -> QWidget:
|
|
"""Create the main content area."""
|
|
c = get_all_colors()
|
|
|
|
content = QWidget()
|
|
content.setStyleSheet(f"""
|
|
QWidget {{
|
|
background-color: {c['bg_primary']};
|
|
}}
|
|
""")
|
|
|
|
layout = QVBoxLayout(content)
|
|
layout.setContentsMargins(24, 24, 24, 24)
|
|
layout.setSpacing(0)
|
|
|
|
# Plugin stack
|
|
self.plugin_stack = QStackedWidget()
|
|
self.plugin_stack.setStyleSheet("background: transparent;")
|
|
layout.addWidget(self.plugin_stack, 1)
|
|
|
|
return content
|
|
|
|
def _setup_tray(self):
|
|
"""Setup system tray icon with better fallback handling."""
|
|
c = get_all_colors()
|
|
|
|
self.tray_icon = QSystemTrayIcon(self)
|
|
|
|
# Try multiple icon sources
|
|
icon_paths = [
|
|
Path("assets/icon.ico"),
|
|
Path("assets/icon.png"),
|
|
Path("assets/icons/eu_utility.svg"),
|
|
Path(__file__).parent.parent / "assets" / "icon.ico",
|
|
Path(__file__).parent.parent / "assets" / "icon.png",
|
|
]
|
|
|
|
icon_set = False
|
|
for icon_path in icon_paths:
|
|
if icon_path.exists():
|
|
self.tray_icon.setIcon(QIcon(str(icon_path)))
|
|
icon_set = True
|
|
break
|
|
|
|
if not icon_set:
|
|
# Use standard icon as fallback
|
|
style = self.style()
|
|
if style:
|
|
standard_icon = style.standardIcon(self.style().StandardPixmap.SP_ComputerIcon)
|
|
self.tray_icon.setIcon(standard_icon)
|
|
|
|
tray_menu = QMenu()
|
|
tray_menu.setStyleSheet(f"""
|
|
QMenu {{
|
|
background-color: {c['bg_elevated']};
|
|
color: {c['text_primary']};
|
|
border: 1px solid {c['border_default']};
|
|
border-radius: {EU_SIZES['radius_md']};
|
|
padding: 8px;
|
|
}}
|
|
QMenu::item {{
|
|
padding: 10px 20px;
|
|
border-radius: {EU_SIZES['radius_sm']};
|
|
}}
|
|
QMenu::item:selected {{
|
|
background-color: {c['accent_orange']};
|
|
}}
|
|
""")
|
|
|
|
show_action = QAction("Show EU-Utility", self)
|
|
show_action.setShortcut("Ctrl+Shift+U")
|
|
show_action.triggered.connect(self.show_overlay)
|
|
tray_menu.addAction(show_action)
|
|
|
|
tray_menu.addSeparator()
|
|
|
|
quit_action = QAction("Quit", self)
|
|
quit_action.triggered.connect(self.quit_app)
|
|
tray_menu.addAction(quit_action)
|
|
|
|
self.tray_icon.setContextMenu(tray_menu)
|
|
self.tray_icon.activated.connect(self._tray_activated)
|
|
self.tray_icon.show()
|
|
|
|
def _setup_shortcuts(self):
|
|
"""Setup keyboard shortcuts."""
|
|
# ESC to close
|
|
esc_shortcut = QShortcut(QKeySequence("Esc"), self)
|
|
esc_shortcut.activated.connect(self.hide_overlay)
|
|
|
|
# Ctrl+T to toggle theme
|
|
theme_shortcut = QShortcut(QKeySequence("Ctrl+T"), self)
|
|
theme_shortcut.activated.connect(self._toggle_theme)
|
|
|
|
# Ctrl+1-9 to switch plugins
|
|
for i in range(1, 10):
|
|
shortcut = QShortcut(QKeySequence(f"Ctrl+{i}"), self)
|
|
shortcut.activated.connect(lambda checked, idx=i-1: self._switch_to_plugin(idx))
|
|
|
|
def _setup_animations(self):
|
|
"""Setup window animations."""
|
|
self._show_anim = QParallelAnimationGroup()
|
|
|
|
# Fade in animation
|
|
self._fade_anim = QPropertyAnimation(self, b"windowOpacity")
|
|
self._fade_anim.setDuration(200)
|
|
self._fade_anim.setStartValue(0.0)
|
|
self._fade_anim.setEndValue(1.0)
|
|
self._fade_anim.setEasingCurve(QEasingCurve.Type.OutQuad)
|
|
|
|
self._show_anim.addAnimation(self._fade_anim)
|
|
|
|
def _load_plugins(self):
|
|
"""Load plugins into sidebar and content stack."""
|
|
plugins_list = list(self.plugin_manager.get_all_plugins().items())
|
|
|
|
if not plugins_list:
|
|
# Show placeholder
|
|
placeholder = self._create_placeholder()
|
|
self.plugin_stack.addWidget(placeholder)
|
|
return
|
|
|
|
for idx, (plugin_id, plugin) in enumerate(plugins_list):
|
|
# Get icon name
|
|
icon_name = get_plugin_icon_name(plugin.name)
|
|
|
|
# Create sidebar button
|
|
btn = SidebarButton(plugin.name, icon_name, self.icon_manager)
|
|
btn.clicked.connect(lambda checked, i=idx: self._on_plugin_selected(i))
|
|
|
|
# Insert before stretch
|
|
self.sidebar_buttons_layout.insertWidget(idx, btn)
|
|
self.sidebar_buttons.append(btn)
|
|
|
|
# Add plugin UI to stack
|
|
try:
|
|
plugin_ui = plugin.get_ui()
|
|
if plugin_ui:
|
|
plugin_ui.setStyleSheet("background: transparent;")
|
|
self.plugin_stack.addWidget(plugin_ui)
|
|
except Exception as e:
|
|
print(f"[Overlay] Error loading UI for {plugin.name}: {e}")
|
|
|
|
# Select first plugin
|
|
if self.sidebar_buttons:
|
|
self._on_plugin_selected(0)
|
|
|
|
def _create_placeholder(self) -> QWidget:
|
|
"""Create placeholder when no plugins are enabled."""
|
|
c = get_all_colors()
|
|
|
|
widget = QWidget()
|
|
layout = QVBoxLayout(widget)
|
|
layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
|
|
icon_label = QLabel()
|
|
icon_pixmap = self.icon_manager.get_pixmap('package', size=48)
|
|
icon_label.setPixmap(icon_pixmap)
|
|
icon_label.setFixedSize(48, 48)
|
|
icon_label.setStyleSheet("margin-bottom: 16px;")
|
|
icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.addWidget(icon_label)
|
|
|
|
title = QLabel("No Plugins Enabled")
|
|
title.setStyleSheet(f"""
|
|
color: {c['text_primary']};
|
|
font-size: {EU_TYPOGRAPHY['size_xl']};
|
|
font-weight: {EU_TYPOGRAPHY['weight_bold']};
|
|
""")
|
|
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.addWidget(title)
|
|
|
|
subtitle = QLabel("Click Settings to enable plugins")
|
|
subtitle.setStyleSheet(f"""
|
|
color: {c['text_secondary']};
|
|
font-size: {EU_TYPOGRAPHY['size_base']};
|
|
""")
|
|
subtitle.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.addWidget(subtitle)
|
|
|
|
settings_btn = AnimatedButton("Open Settings")
|
|
settings_btn.setStyleSheet(get_button_style('primary', 'md'))
|
|
settings_btn.clicked.connect(self._open_settings)
|
|
settings_btn.setMaximumWidth(200)
|
|
layout.addWidget(settings_btn, alignment=Qt.AlignmentFlag.AlignCenter)
|
|
|
|
return widget
|
|
|
|
def _on_plugin_selected(self, index: int):
|
|
"""Handle plugin selection."""
|
|
if index < 0 or index >= len(self.sidebar_buttons):
|
|
return
|
|
|
|
# Update button states
|
|
for i, btn in enumerate(self.sidebar_buttons):
|
|
btn.set_selected(i == index)
|
|
|
|
# Switch content with animation
|
|
self._animate_plugin_switch(index)
|
|
self.current_plugin_index = index
|
|
|
|
def _animate_plugin_switch(self, index: int):
|
|
"""Animate plugin content switch."""
|
|
# Simple crossfade could be added here
|
|
self.plugin_stack.setCurrentIndex(index)
|
|
|
|
def _switch_to_plugin(self, index: int):
|
|
"""Switch to plugin by index (for keyboard shortcuts)."""
|
|
if 0 <= index < len(self.sidebar_buttons):
|
|
self._on_plugin_selected(index)
|
|
|
|
def _toggle_theme(self):
|
|
"""Toggle between dark and light themes."""
|
|
new_theme = "light" if EUTheme.is_dark() else "dark"
|
|
EUTheme.set_theme(new_theme)
|
|
|
|
# Update theme button icon
|
|
theme_icon = 'sun' if EUTheme.is_dark() else 'moon'
|
|
theme_pixmap = self.icon_manager.get_pixmap(theme_icon, size=20)
|
|
self.theme_btn.setIcon(QIcon(theme_pixmap))
|
|
|
|
# Reapply styles
|
|
self.setStyleSheet(get_global_stylesheet())
|
|
self._refresh_ui()
|
|
|
|
# Emit signal
|
|
self.theme_changed.emit(new_theme)
|
|
|
|
def _refresh_ui(self):
|
|
"""Refresh UI after theme change."""
|
|
c = get_all_colors()
|
|
|
|
# Update header
|
|
header = self.findChild(QFrame, "header")
|
|
if header:
|
|
header.setStyleSheet(f"""
|
|
QFrame#header {{
|
|
background-color: {c['bg_secondary']};
|
|
border-bottom: 1px solid {c['border_default']};
|
|
}}
|
|
""")
|
|
|
|
# Update sidebar buttons
|
|
for btn in self.sidebar_buttons:
|
|
btn._update_style()
|
|
|
|
def _tray_activated(self, reason):
|
|
"""Handle tray activation."""
|
|
if reason == QSystemTrayIcon.ActivationReason.DoubleClick:
|
|
self.toggle_overlay()
|
|
|
|
def show_overlay(self):
|
|
"""Show overlay with animation."""
|
|
self.show()
|
|
self.raise_()
|
|
self.activateWindow()
|
|
self.is_visible = True
|
|
self.visibility_changed.emit(True)
|
|
|
|
# Start fade animation
|
|
if self._fade_anim:
|
|
self._fade_anim.start()
|
|
|
|
def hide_overlay(self):
|
|
"""Hide overlay."""
|
|
self.hide()
|
|
self.is_visible = False
|
|
self.visibility_changed.emit(False)
|
|
|
|
def toggle_overlay(self):
|
|
"""Toggle overlay visibility."""
|
|
if self.is_visible:
|
|
self.hide_overlay()
|
|
else:
|
|
self.show_overlay()
|
|
|
|
def quit_app(self):
|
|
"""Quit the application."""
|
|
if self.plugin_manager:
|
|
self.plugin_manager.shutdown_all()
|
|
self.tray_icon.hide()
|
|
QApplication.quit()
|
|
|
|
def _open_settings(self):
|
|
"""Open settings dialog."""
|
|
from PyQt6.QtWidgets import QDialog
|
|
|
|
c = get_all_colors()
|
|
|
|
dialog = QDialog(self)
|
|
dialog.setWindowTitle("EU-Utility Settings")
|
|
dialog.setMinimumSize(550, 450)
|
|
dialog.setStyleSheet(f"""
|
|
QDialog {{
|
|
background-color: {c['bg_primary']};
|
|
}}
|
|
""")
|
|
|
|
layout = QVBoxLayout(dialog)
|
|
layout.setSpacing(16)
|
|
layout.setContentsMargins(24, 24, 24, 24)
|
|
|
|
# Tabs
|
|
tabs = QTabWidget()
|
|
tabs.setStyleSheet(get_table_style())
|
|
|
|
# Plugins tab
|
|
plugins_tab = self._create_plugins_settings_tab()
|
|
tabs.addTab(plugins_tab, "Plugins")
|
|
|
|
# Hotkeys tab
|
|
hotkeys_tab = self._create_hotkeys_settings_tab()
|
|
tabs.addTab(hotkeys_tab, "Hotkeys")
|
|
|
|
# Appearance tab
|
|
appearance_tab = self._create_appearance_settings_tab()
|
|
tabs.addTab(appearance_tab, "Appearance")
|
|
|
|
# About tab
|
|
about_tab = self._create_about_tab()
|
|
tabs.addTab(about_tab, "About")
|
|
|
|
layout.addWidget(tabs)
|
|
|
|
# Buttons
|
|
btn_layout = QHBoxLayout()
|
|
btn_layout.addStretch()
|
|
|
|
cancel_btn = AnimatedButton("Cancel")
|
|
cancel_btn.setStyleSheet(get_button_style('ghost'))
|
|
cancel_btn.clicked.connect(dialog.reject)
|
|
btn_layout.addWidget(cancel_btn)
|
|
|
|
save_btn = AnimatedButton("Save Changes")
|
|
save_btn.setStyleSheet(get_button_style('primary'))
|
|
save_btn.clicked.connect(lambda: self._save_settings(dialog))
|
|
save_btn.setDefault(True)
|
|
btn_layout.addWidget(save_btn)
|
|
|
|
layout.addLayout(btn_layout)
|
|
|
|
dialog.exec()
|
|
|
|
def _create_plugins_settings_tab(self) -> QWidget:
|
|
"""Create plugins settings tab with improved UI and categorization."""
|
|
c = get_all_colors()
|
|
|
|
tab = QWidget()
|
|
layout = QVBoxLayout(tab)
|
|
layout.setSpacing(16)
|
|
layout.setContentsMargins(16, 16, 16, 16)
|
|
|
|
# Header with info
|
|
header_frame = QFrame()
|
|
header_frame.setStyleSheet(f"""
|
|
QFrame {{
|
|
background-color: {c['bg_secondary']};
|
|
border-left: 3px solid {c['accent_orange']};
|
|
border-radius: 4px;
|
|
padding: 12px;
|
|
}}
|
|
""")
|
|
header_layout = QVBoxLayout(header_frame)
|
|
header_layout.setSpacing(4)
|
|
|
|
info_title = QLabel("Plugin Management")
|
|
info_title.setStyleSheet(f"color: {c['text_primary']}; font-weight: bold; font-size: 13px;")
|
|
header_layout.addWidget(info_title)
|
|
|
|
info_desc = QLabel("Enable or disable plugins. Changes take effect after restarting the overlay.")
|
|
info_desc.setStyleSheet(f"color: {c['text_secondary']}; font-size: 11px;")
|
|
info_desc.setWordWrap(True)
|
|
header_layout.addWidget(info_desc)
|
|
|
|
layout.addWidget(header_frame)
|
|
|
|
# Legend
|
|
legend_frame = QFrame()
|
|
legend_layout = QHBoxLayout(legend_frame)
|
|
legend_layout.setSpacing(16)
|
|
|
|
core_label = QLabel("● Core")
|
|
core_label.setStyleSheet("color: #4ecdc4; font-size: 11px; font-weight: bold;")
|
|
legend_layout.addWidget(core_label)
|
|
|
|
test_label = QLabel("● Test")
|
|
test_label.setStyleSheet("color: #ff8c42; font-size: 11px; font-weight: bold;")
|
|
legend_layout.addWidget(test_label)
|
|
|
|
other_label = QLabel("● Other")
|
|
other_label.setStyleSheet("color: #a0aec0; font-size: 11px;")
|
|
legend_layout.addWidget(other_label)
|
|
|
|
legend_layout.addStretch()
|
|
layout.addWidget(legend_frame)
|
|
|
|
# Plugin list container
|
|
self.settings_checkboxes = {}
|
|
|
|
if self.plugin_manager:
|
|
all_plugins = self.plugin_manager.get_all_discovered_plugins()
|
|
|
|
# Categorize plugins
|
|
core_plugins = []
|
|
test_plugins = []
|
|
other_plugins = []
|
|
|
|
for plugin_id, plugin_class in all_plugins.items():
|
|
try:
|
|
name = getattr(plugin_class, 'name', plugin_id.split('.')[-1])
|
|
if 'test' in plugin_id.lower() or name.lower().endswith('test'):
|
|
test_plugins.append((plugin_id, plugin_class))
|
|
elif plugin_id.startswith(('core.', 'plugins.skill_scanner', 'plugins.loot_tracker',
|
|
'plugins.mining_helper', 'plugins.chat_logger',
|
|
'plugins.global_tracker', 'plugins.nexus_search',
|
|
'plugins.universal_search', 'plugins.calculator',
|
|
'plugins.settings', 'plugins.dashboard')):
|
|
core_plugins.append((plugin_id, plugin_class))
|
|
else:
|
|
other_plugins.append((plugin_id, plugin_class))
|
|
except:
|
|
other_plugins.append((plugin_id, plugin_class))
|
|
|
|
# Sort each category
|
|
def get_name(item):
|
|
try:
|
|
return getattr(item[1], 'name', item[0]).lower()
|
|
except:
|
|
return item[0].lower()
|
|
|
|
core_plugins.sort(key=get_name)
|
|
test_plugins.sort(key=get_name)
|
|
other_plugins.sort(key=get_name)
|
|
|
|
# Create scroll area for plugins
|
|
scroll = QScrollArea()
|
|
scroll.setWidgetResizable(True)
|
|
scroll.setFrameShape(QFrame.Shape.NoFrame)
|
|
scroll.setStyleSheet(f"""
|
|
QScrollArea {{
|
|
background-color: transparent;
|
|
border: none;
|
|
}}
|
|
QScrollBar:vertical {{
|
|
background-color: {c['bg_secondary']};
|
|
width: 10px;
|
|
border-radius: 5px;
|
|
}}
|
|
QScrollBar::handle:vertical {{
|
|
background-color: {c['accent_orange']};
|
|
border-radius: 5px;
|
|
min-height: 30px;
|
|
}}
|
|
""")
|
|
|
|
plugins_container = QWidget()
|
|
plugins_layout = QVBoxLayout(plugins_container)
|
|
plugins_layout.setSpacing(8)
|
|
plugins_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
|
|
|
# Add Core Plugins section
|
|
if core_plugins:
|
|
core_header = QLabel("CORE PLUGINS")
|
|
core_header.setStyleSheet(f"""
|
|
color: #4ecdc4;
|
|
font-weight: bold;
|
|
font-size: 10px;
|
|
padding: 8px 4px 4px 4px;
|
|
border-bottom: 1px solid {c['border_color']};
|
|
""")
|
|
plugins_layout.addWidget(core_header)
|
|
|
|
for plugin_id, plugin_class in core_plugins:
|
|
self._add_plugin_row(plugins_layout, plugin_id, plugin_class, c, "Core")
|
|
|
|
# Add Test Plugins section
|
|
if test_plugins:
|
|
test_header = QLabel("TEST PLUGINS")
|
|
test_header.setStyleSheet(f"""
|
|
color: #ff8c42;
|
|
font-weight: bold;
|
|
font-size: 10px;
|
|
padding: 16px 4px 4px 4px;
|
|
border-bottom: 1px solid {c['border_color']};
|
|
""")
|
|
plugins_layout.addWidget(test_header)
|
|
|
|
for plugin_id, plugin_class in test_plugins:
|
|
self._add_plugin_row(plugins_layout, plugin_id, plugin_class, c, "Test")
|
|
|
|
# Add Other Plugins section
|
|
if other_plugins:
|
|
other_header = QLabel("OTHER PLUGINS")
|
|
other_header.setStyleSheet(f"""
|
|
color: {c['text_muted']};
|
|
font-weight: bold;
|
|
font-size: 10px;
|
|
padding: 16px 4px 4px 4px;
|
|
border-bottom: 1px solid {c['border_color']};
|
|
""")
|
|
plugins_layout.addWidget(other_header)
|
|
|
|
for plugin_id, plugin_class in other_plugins:
|
|
self._add_plugin_row(plugins_layout, plugin_id, plugin_class, c, None)
|
|
|
|
plugins_layout.addStretch()
|
|
scroll.setWidget(plugins_container)
|
|
layout.addWidget(scroll, 1) # Stretch factor 1
|
|
|
|
return tab
|
|
|
|
def _add_plugin_row(self, layout, plugin_id, plugin_class, colors, prefix):
|
|
"""Add a plugin row to the layout."""
|
|
try:
|
|
row_widget = QFrame()
|
|
row_widget.setStyleSheet(f"""
|
|
QFrame {{
|
|
background-color: {colors['bg_secondary']};
|
|
border-radius: 4px;
|
|
padding: 2px;
|
|
}}
|
|
QFrame:hover {{
|
|
background-color: {colors['bg_hover']};
|
|
}}
|
|
""")
|
|
row_layout = QHBoxLayout(row_widget)
|
|
row_layout.setSpacing(12)
|
|
row_layout.setContentsMargins(10, 8, 10, 8)
|
|
|
|
# Get plugin attributes
|
|
name = getattr(plugin_class, 'name', plugin_id.split('.')[-1])
|
|
version = getattr(plugin_class, 'version', '?.?.?')
|
|
description = getattr(plugin_class, 'description', 'No description')
|
|
author = getattr(plugin_class, 'author', 'Unknown')
|
|
|
|
# Add prefix to name
|
|
display_name = f"{prefix}-{name}" if prefix else name
|
|
|
|
# Checkbox
|
|
cb = QCheckBox(f"{display_name}")
|
|
cb.setChecked(self.plugin_manager.is_plugin_enabled(plugin_id))
|
|
cb.setStyleSheet(f"""
|
|
QCheckBox {{
|
|
color: {colors['text_primary']};
|
|
font-weight: bold;
|
|
spacing: 8px;
|
|
}}
|
|
QCheckBox::indicator {{
|
|
width: 16px;
|
|
height: 16px;
|
|
border-radius: 3px;
|
|
border: 2px solid {colors['accent_orange']};
|
|
}}
|
|
QCheckBox::indicator:checked {{
|
|
background-color: {colors['accent_orange']};
|
|
}}
|
|
""")
|
|
self.settings_checkboxes[plugin_id] = cb
|
|
row_layout.addWidget(cb)
|
|
|
|
# Version badge
|
|
version_label = QLabel(f"v{version}")
|
|
version_label.setStyleSheet(f"""
|
|
color: {colors['accent_orange']};
|
|
font-size: 9px;
|
|
font-weight: bold;
|
|
background-color: {colors['bg_primary']};
|
|
padding: 2px 6px;
|
|
border-radius: 3px;
|
|
""")
|
|
row_layout.addWidget(version_label)
|
|
|
|
# Description
|
|
desc_label = QLabel(description)
|
|
desc_label.setStyleSheet(f"""
|
|
color: {colors['text_muted']};
|
|
font-size: 11px;
|
|
""")
|
|
desc_label.setWordWrap(True)
|
|
row_layout.addWidget(desc_label, 1)
|
|
|
|
# Author (small)
|
|
author_label = QLabel(f"by {author}")
|
|
author_label.setStyleSheet(f"""
|
|
color: {colors['text_secondary']};
|
|
font-size: 9px;
|
|
""")
|
|
row_layout.addWidget(author_label)
|
|
|
|
layout.addWidget(row_widget)
|
|
|
|
except Exception as e:
|
|
print(f"[Overlay] Error creating settings for {plugin_id}: {e}")
|
|
|
|
def _create_hotkeys_settings_tab(self) -> QWidget:
|
|
"""Create hotkeys settings tab."""
|
|
c = get_all_colors()
|
|
|
|
tab = QWidget()
|
|
layout = QVBoxLayout(tab)
|
|
layout.setSpacing(16)
|
|
layout.setContentsMargins(16, 16, 16, 16)
|
|
|
|
# Header
|
|
header_frame = QFrame()
|
|
header_frame.setStyleSheet(f"""
|
|
QFrame {{
|
|
background-color: {c['bg_secondary']};
|
|
border-left: 3px solid {c['accent_orange']};
|
|
border-radius: 4px;
|
|
padding: 12px;
|
|
}}
|
|
""")
|
|
header_layout = QVBoxLayout(header_frame)
|
|
|
|
info_title = QLabel("Keyboard Shortcuts")
|
|
info_title.setStyleSheet(f"color: {c['text_primary']}; font-weight: bold; font-size: 13px;")
|
|
header_layout.addWidget(info_title)
|
|
|
|
info_desc = QLabel("Configure global and local keyboard shortcuts. Global hotkeys work even when the overlay is hidden.")
|
|
info_desc.setStyleSheet(f"color: {c['text_secondary']}; font-size: 11px;")
|
|
info_desc.setWordWrap(True)
|
|
header_layout.addWidget(info_desc)
|
|
|
|
layout.addWidget(header_frame)
|
|
|
|
# Hotkeys list container
|
|
scroll = QScrollArea()
|
|
scroll.setWidgetResizable(True)
|
|
scroll.setFrameShape(QFrame.Shape.NoFrame)
|
|
scroll.setStyleSheet(f"""
|
|
QScrollArea {{
|
|
background-color: transparent;
|
|
border: none;
|
|
}}
|
|
QScrollBar:vertical {{
|
|
background-color: {c['bg_secondary']};
|
|
width: 10px;
|
|
border-radius: 5px;
|
|
}}
|
|
QScrollBar::handle:vertical {{
|
|
background-color: {c['accent_orange']};
|
|
border-radius: 5px;
|
|
min-height: 30px;
|
|
}}
|
|
""")
|
|
|
|
hotkeys_container = QWidget()
|
|
hotkeys_layout = QVBoxLayout(hotkeys_container)
|
|
hotkeys_layout.setSpacing(8)
|
|
hotkeys_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
|
|
|
# Get hotkey manager
|
|
try:
|
|
from core.hotkey_manager import HotkeyManager
|
|
hotkey_manager = HotkeyManager()
|
|
hotkeys = hotkey_manager.get_all_hotkeys()
|
|
|
|
# Group by scope
|
|
scopes = {
|
|
'global': ('Global Hotkeys', 'Work even when overlay is hidden', '#4ecdc4'),
|
|
'local': ('Local Hotkeys', 'Work only when app is focused', '#a0aec0'),
|
|
'overlay': ('Overlay Hotkeys', 'Work only when overlay is visible', '#ff8c42'),
|
|
}
|
|
|
|
for scope_key, (scope_name, scope_desc, scope_color) in scopes.items():
|
|
scope_hotkeys = {k: v for k, v in hotkeys.items() if v.scope == scope_key}
|
|
|
|
if scope_hotkeys:
|
|
# Section header
|
|
scope_header = QLabel(f"{scope_name.upper()}")
|
|
scope_header.setStyleSheet(f"""
|
|
color: {scope_color};
|
|
font-weight: bold;
|
|
font-size: 10px;
|
|
padding: 12px 4px 4px 4px;
|
|
border-bottom: 1px solid {c['border_color']};
|
|
""")
|
|
hotkeys_layout.addWidget(scope_header)
|
|
|
|
scope_subheader = QLabel(scope_desc)
|
|
scope_subheader.setStyleSheet(f"color: {c['text_muted']}; font-size: 9px; padding-left: 4px;")
|
|
hotkeys_layout.addWidget(scope_subheader)
|
|
|
|
# Add each hotkey
|
|
for action, config in scope_hotkeys.items():
|
|
row_widget = QFrame()
|
|
row_widget.setStyleSheet(f"""
|
|
QFrame {{
|
|
background-color: {c['bg_secondary']};
|
|
border-radius: 4px;
|
|
padding: 2px;
|
|
}}
|
|
""")
|
|
row_layout = QHBoxLayout(row_widget)
|
|
row_layout.setSpacing(12)
|
|
row_layout.setContentsMargins(10, 8, 10, 8)
|
|
|
|
# Description
|
|
desc_label = QLabel(config.description)
|
|
desc_label.setStyleSheet(f"color: {c['text_primary']}; font-size: 12px;")
|
|
row_layout.addWidget(desc_label, 1)
|
|
|
|
# Key combo display
|
|
key_label = QLabel(config.keys.upper())
|
|
key_label.setStyleSheet(f"""
|
|
color: {c['accent_orange']};
|
|
font-weight: bold;
|
|
font-size: 11px;
|
|
background-color: {c['bg_primary']};
|
|
padding: 4px 10px;
|
|
border-radius: 4px;
|
|
border: 1px solid {c['border_color']};
|
|
""")
|
|
row_layout.addWidget(key_label)
|
|
|
|
# Enable checkbox
|
|
enable_cb = QCheckBox()
|
|
enable_cb.setChecked(config.enabled)
|
|
enable_cb.setStyleSheet(f"""
|
|
QCheckBox::indicator {{
|
|
width: 16px;
|
|
height: 16px;
|
|
border-radius: 3px;
|
|
border: 2px solid {c['accent_orange']};
|
|
}}
|
|
QCheckBox::indicator:checked {{
|
|
background-color: {c['accent_orange']};
|
|
}}
|
|
""")
|
|
row_layout.addWidget(enable_cb)
|
|
|
|
hotkeys_layout.addWidget(row_widget)
|
|
|
|
except Exception as e:
|
|
error_label = QLabel(f"Error loading hotkeys: {e}")
|
|
error_label.setStyleSheet(f"color: #ff6b6b;")
|
|
hotkeys_layout.addWidget(error_label)
|
|
|
|
hotkeys_layout.addStretch()
|
|
scroll.setWidget(hotkeys_container)
|
|
layout.addWidget(scroll, 1)
|
|
|
|
# Reset button
|
|
reset_btn = QPushButton("Reset to Defaults")
|
|
reset_btn.setStyleSheet(get_button_style('secondary'))
|
|
reset_btn.clicked.connect(self._reset_hotkeys)
|
|
layout.addWidget(reset_btn)
|
|
|
|
return tab
|
|
|
|
def _reset_hotkeys(self):
|
|
"""Reset hotkeys to defaults."""
|
|
try:
|
|
from core.hotkey_manager import HotkeyManager
|
|
hotkey_manager = HotkeyManager()
|
|
hotkey_manager.reset_to_defaults()
|
|
self.notify_info("Hotkeys Reset", "All hotkeys have been reset to default values.")
|
|
except Exception as e:
|
|
self.notify_error("Error", f"Failed to reset hotkeys: {e}")
|
|
|
|
def _create_appearance_settings_tab(self) -> QWidget:
|
|
"""Create appearance settings tab."""
|
|
c = get_all_colors()
|
|
|
|
tab = QWidget()
|
|
layout = QVBoxLayout(tab)
|
|
layout.setSpacing(16)
|
|
|
|
# Theme selection
|
|
theme_label = QLabel("Theme")
|
|
theme_label.setStyleSheet(f"""
|
|
color: {c['text_primary']};
|
|
font-weight: {EU_TYPOGRAPHY['weight_semibold']};
|
|
""")
|
|
layout.addWidget(theme_label)
|
|
|
|
theme_desc = QLabel("Choose between dark and light appearance")
|
|
theme_desc.setStyleSheet(f"color: {c['text_secondary']};")
|
|
layout.addWidget(theme_desc)
|
|
|
|
# Theme buttons
|
|
theme_layout = QHBoxLayout()
|
|
|
|
dark_btn = QPushButton("Dark")
|
|
dark_btn.setCheckable(True)
|
|
dark_btn.setChecked(EUTheme.is_dark())
|
|
dark_btn.setStyleSheet(get_button_style('secondary' if not EUTheme.is_dark() else 'primary'))
|
|
dark_btn.clicked.connect(lambda: self._set_theme_from_settings("dark"))
|
|
theme_layout.addWidget(dark_btn)
|
|
|
|
light_btn = QPushButton("Light")
|
|
light_btn.setCheckable(True)
|
|
light_btn.setChecked(not EUTheme.is_dark())
|
|
light_btn.setStyleSheet(get_button_style('primary' if not EUTheme.is_dark() else 'secondary'))
|
|
light_btn.clicked.connect(lambda: self._set_theme_from_settings("light"))
|
|
theme_layout.addWidget(light_btn)
|
|
|
|
theme_layout.addStretch()
|
|
layout.addLayout(theme_layout)
|
|
|
|
layout.addStretch()
|
|
return tab
|
|
|
|
def _set_theme_from_settings(self, theme: str):
|
|
"""Set theme from settings dialog."""
|
|
EUTheme.set_theme(theme)
|
|
# Will be applied when settings are saved
|
|
|
|
def _create_about_tab(self) -> QWidget:
|
|
"""Create about tab."""
|
|
c = get_all_colors()
|
|
|
|
tab = QWidget()
|
|
layout = QVBoxLayout(tab)
|
|
layout.setSpacing(16)
|
|
layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
|
|
# Logo/Icon
|
|
icon_label = QLabel()
|
|
icon_pixmap = self.icon_manager.get_pixmap('target', size=64)
|
|
icon_label.setPixmap(icon_pixmap)
|
|
icon_label.setFixedSize(64, 64)
|
|
icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.addWidget(icon_label)
|
|
|
|
# Title
|
|
title = QLabel("EU-Utility")
|
|
title.setStyleSheet(f"""
|
|
color: {c['text_primary']};
|
|
font-size: {EU_TYPOGRAPHY['size_3xl']};
|
|
font-weight: {EU_TYPOGRAPHY['weight_bold']};
|
|
""")
|
|
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.addWidget(title)
|
|
|
|
# Version
|
|
version = QLabel("Version 2.0")
|
|
version.setStyleSheet(f"color: {c['text_secondary']};")
|
|
version.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.addWidget(version)
|
|
|
|
# Description
|
|
desc = QLabel("A utility overlay for Entropia Universe")
|
|
desc.setStyleSheet(f"color: {c['text_muted']};")
|
|
desc.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.addWidget(desc)
|
|
|
|
# Hotkeys
|
|
hotkeys = QLabel("""
|
|
<b>Keyboard Shortcuts:</b><br>
|
|
Ctrl+Shift+U — Toggle overlay<br>
|
|
Ctrl+Shift+H — Hide all overlays<br>
|
|
Ctrl+T — Toggle theme<br>
|
|
Ctrl+1-9 — Switch plugins<br>
|
|
ESC — Close overlay
|
|
""")
|
|
hotkeys.setStyleSheet(f"""
|
|
color: {c['text_secondary']};
|
|
margin-top: 24px;
|
|
line-height: 1.8;
|
|
""")
|
|
hotkeys.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
hotkeys.setTextFormat(Qt.TextFormat.RichText)
|
|
layout.addWidget(hotkeys)
|
|
|
|
layout.addStretch()
|
|
return tab
|
|
|
|
def _save_settings(self, dialog):
|
|
"""Save plugin settings."""
|
|
if not self.plugin_manager:
|
|
dialog.accept()
|
|
return
|
|
|
|
# Apply theme
|
|
self._refresh_ui()
|
|
self.setStyleSheet(get_global_stylesheet())
|
|
|
|
# Save plugin enable/disable
|
|
for plugin_id, cb in self.settings_checkboxes.items():
|
|
if cb.isChecked():
|
|
self.plugin_manager.enable_plugin(plugin_id)
|
|
else:
|
|
self.plugin_manager.disable_plugin(plugin_id)
|
|
|
|
# Reload plugins
|
|
self._reload_plugins()
|
|
dialog.accept()
|
|
|
|
def _reload_plugins(self):
|
|
"""Reload plugins after settings change."""
|
|
# Clear existing
|
|
while self.plugin_stack.count() > 0:
|
|
widget = self.plugin_stack.widget(0)
|
|
self.plugin_stack.removeWidget(widget)
|
|
|
|
# Clear sidebar buttons
|
|
for btn in self.sidebar_buttons:
|
|
btn.deleteLater()
|
|
self.sidebar_buttons.clear()
|
|
|
|
# Reload
|
|
self._load_plugins()
|
|
|
|
def resizeEvent(self, event):
|
|
"""Handle resize for responsive behavior."""
|
|
super().resizeEvent(event)
|
|
|
|
width = self.width()
|
|
|
|
# Responsive sidebar behavior
|
|
if width < ResponsiveHelper.BREAKPOINTS['md']:
|
|
# Collapse sidebar on small screens
|
|
self.sidebar.setVisible(False)
|
|
else:
|
|
self.sidebar.setVisible(True)
|
|
|
|
def keyPressEvent(self, event):
|
|
"""Handle key press events."""
|
|
if event.key() == Qt.Key.Key_Escape:
|
|
self.hide_overlay()
|
|
else:
|
|
super().keyPressEvent(event)
|