""" 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.""" c = get_all_colors() self.tray_icon = QSystemTrayIcon(self) icon_path = Path("assets/icon.ico") if icon_path.exists(): self.tray_icon.setIcon(QIcon(str(icon_path))) 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") # 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_primary']}; 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_primary']}; 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_primary']}; }} QCheckBox::indicator:checked {{ background-color: {colors['accent_primary']}; }} """) 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_primary']}; 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_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(""" Keyboard Shortcuts:
Ctrl+Shift+U — Toggle overlay
Ctrl+Shift+H — Hide all overlays
Ctrl+T — Toggle theme
Ctrl+1-9 — Switch plugins
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)