""" 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_with_tabs() 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) # 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 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 with proper callback capture btn = SidebarButton(plugin.name, icon_name, self.icon_manager) # Use functools.partial to properly capture idx by value from functools import partial btn.clicked.connect(partial(self._on_plugin_selected, idx)) # 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") # Plugin Store tab store_tab = self._create_plugin_store_tab() tabs.addTab(store_tab, "🔌 Store") # 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.dashboard')) and 'settings' not in plugin_id: 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_default']}; """) 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_default']}; """) 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_default']}; """) 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 _create_plugin_store_tab(self): """Create plugin store tab for browsing and installing plugins.""" from core.plugin_store import PluginStoreUI if hasattr(self, 'plugin_manager') and self.plugin_manager: return PluginStoreUI(self.plugin_manager) # Fallback - show error from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel tab = QWidget() layout = QVBoxLayout(tab) error = QLabel("Plugin Manager not available. Cannot load Plugin Store.") error.setStyleSheet("color: #ff4757; font-size: 14px;") layout.addWidget(error) 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_default']}; """) 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_default']}; """) 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(""" 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 with dependency checking.""" if not self.plugin_manager: dialog.accept() return # Apply theme self._refresh_ui() self.setStyleSheet(get_global_stylesheet()) # Check for newly enabled plugins with dependencies from core.plugin_dependency_manager import get_dependency_manager dep_manager = get_dependency_manager() plugins_to_enable = [] for plugin_id, cb in self.settings_checkboxes.items(): if cb.isChecked(): # Check if this is a newly enabled plugin if not self.plugin_manager.is_plugin_enabled(plugin_id): plugins_to_enable.append(plugin_id) self.plugin_manager.enable_plugin(plugin_id) else: self.plugin_manager.disable_plugin(plugin_id) # Check dependencies for newly enabled plugins for plugin_id in plugins_to_enable: # Get plugin class all_plugins = self.plugin_manager.get_all_discovered_plugins() if plugin_id in all_plugins: plugin_class = all_plugins[plugin_id] if dep_manager.has_dependencies(plugin_class): pip_installed, pip_missing, plugin_installed, plugin_missing = dep_manager.check_all_dependencies(plugin_class) # Check for plugin dependencies that need to be enabled first if plugin_missing: plugin_dep_text = dep_manager.get_plugin_dependencies_text(plugin_class) from PyQt6.QtWidgets import QMessageBox msg_box = QMessageBox(self) msg_box.setWindowTitle("Plugin Dependencies Required") msg_box.setText(f"Plugin '{plugin_class.name}' requires other plugins to be enabled:\n\n{plugin_dep_text}") msg_box.setInformativeText("These plugins must be enabled before this plugin can work properly.") msg_box.setStandardButtons(QMessageBox.StandardButton.Ok) msg_box.exec() # Handle pip package installation if pip_missing: # Show dependency dialog dep_text = dep_manager.get_missing_dependencies_text(plugin_class) from PyQt6.QtWidgets import QMessageBox msg_box = QMessageBox(self) msg_box.setWindowTitle("Install Dependencies?") msg_box.setText(f"Plugin '{plugin_class.name}' requires additional dependencies:\n\n{dep_text}") msg_box.setInformativeText("Would you like to install them now?") msg_box.setStandardButtons( QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No ) msg_box.setDefaultButton(QMessageBox.StandardButton.Yes) reply = msg_box.exec() if reply == QMessageBox.StandardButton.Yes: # Install dependencies from PyQt6.QtWidgets import QProgressDialog from PyQt6.QtCore import Qt progress = QProgressDialog( f"Installing dependencies for {plugin_class.name}...", "Cancel", 0, len(pip_missing), self ) progress.setWindowModality(Qt.WindowModality.WindowModal) progress.setWindowTitle("Installing Dependencies") current = 0 for dep in pip_missing: if progress.wasCanceled(): break progress.setLabelText(f"Installing {dep.name}...") progress.setValue(current) success, msg = dep_manager.install_dependency(dep.name) if not success: QMessageBox.warning( self, "Installation Failed", f"Failed to install {dep.name}:\n{msg}" ) current += 1 progress.setValue(len(pip_missing)) # 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) # Add these methods to overlay_window.py def _create_content_area_with_tabs(self) -> QWidget: """Create the main content area with top tabs.""" c = get_all_colors() content = QWidget() content.setStyleSheet(f""" QWidget {{ background-color: {c['bg_primary']}; }} """) layout = QVBoxLayout(content) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) # Tab bar self.tab_bar = QWidget() self.tab_bar.setFixedHeight(48) self.tab_bar.setStyleSheet(f""" QWidget {{ background-color: {c['bg_secondary']}; border-bottom: 1px solid {c['border_default']}; }} """) from PyQt6.QtWidgets import QHBoxLayout, QPushButton tab_layout = QHBoxLayout(self.tab_bar) tab_layout.setContentsMargins(16, 0, 16, 0) tab_layout.setSpacing(0) # Create tab buttons self.tab_buttons = {} tabs = [ ('plugins', '🔌 Plugins', 0), ('widgets', '🎨 Widgets', 1), ('settings', 'âš™ī¸ Settings', 2), ] for tab_id, tab_name, idx in tabs: btn = QPushButton(tab_name) btn.setCheckable(True) btn.setFixedHeight(36) btn.setStyleSheet(f""" QPushButton {{ background-color: transparent; color: {c['text_secondary']}; font-size: {EU_TYPOGRAPHY['size_sm']}; font-weight: {EU_TYPOGRAPHY['weight_medium']}; border: none; border-bottom: 3px solid transparent; padding: 0 20px; margin: 0 4px; }} QPushButton:hover {{ color: {c['text_primary']}; background-color: {c['bg_hover']}; }} QPushButton:checked {{ color: {c['accent_orange']}; border-bottom: 3px solid {c['accent_orange']}; font-weight: {EU_TYPOGRAPHY['weight_bold']}; }} """) btn.clicked.connect(lambda checked, t=tab_id: self._switch_tab(t)) self.tab_buttons[tab_id] = btn tab_layout.addWidget(btn) tab_layout.addStretch() layout.addWidget(self.tab_bar) # Tab content stack self.tab_stack = QStackedWidget() self.tab_stack.setStyleSheet("background: transparent;") # Plugins tab content self.plugins_tab = self._create_plugins_tab() self.tab_stack.addWidget(self.plugins_tab) # Widgets tab content self.widgets_tab = self._create_widgets_tab() self.tab_stack.addWidget(self.widgets_tab) # Settings tab content self.settings_tab = self._create_settings_tab() self.tab_stack.addWidget(self.settings_tab) layout.addWidget(self.tab_stack, 1) # Set initial tab self._switch_tab('plugins') return content def _switch_tab(self, tab_id: str): """Switch to the specified tab.""" # Update button states for btn_id, btn in self.tab_buttons.items(): btn.setChecked(btn_id == tab_id) # Update stack tab_index = {'plugins': 0, 'widgets': 1, 'settings': 2} if tab_id in tab_index: self.tab_stack.setCurrentIndex(tab_index[tab_id]) # Refresh widgets tab when switching to it if tab_id == 'widgets': self._refresh_widgets_tab() def _create_plugins_tab(self) -> QWidget: """Create the plugins tab content.""" c = get_all_colors() tab = QWidget() layout = QVBoxLayout(tab) layout.setContentsMargins(24, 24, 24, 24) layout.setSpacing(0) # Plugin stack (where plugin UIs are shown) self.plugin_stack = QStackedWidget() self.plugin_stack.setStyleSheet("background: transparent;") layout.addWidget(self.plugin_stack, 1) return tab def _create_widgets_tab(self) -> QWidget: """Create the widgets tab content.""" c = get_all_colors() tab = QWidget() layout = QVBoxLayout(tab) layout.setContentsMargins(24, 24, 24, 24) layout.setSpacing(16) # Header header = QLabel("🎨 Widgets") header.setStyleSheet(f""" font-size: 24px; font-weight: {EU_TYPOGRAPHY['weight_bold']}; color: {c['text_primary']}; """) layout.addWidget(header) # Description desc = QLabel("Add overlay widgets to your game. Install plugins to get more widgets.") desc.setStyleSheet(f"color: {c['text_secondary']}; font-size: 12px;") desc.setWordWrap(True) layout.addWidget(desc) # Get registered widgets from core.widget_registry import get_widget_registry registry = get_widget_registry() widgets = registry.get_all_widgets() if widgets: # Available widgets section available_header = QLabel("Available Widgets") available_header.setStyleSheet(f""" color: {c['text_primary']}; font-weight: {EU_TYPOGRAPHY['weight_bold']}; font-size: 14px; margin-top: 16px; padding-bottom: 8px; border-bottom: 1px solid {c['border_default']}; """) layout.addWidget(available_header) # Create buttons for each registered widget for widget_info in widgets: widget_btn = self._create_widget_button( f"{widget_info.icon} {widget_info.name}", widget_info.description, lambda wid=widget_info.id: self._add_registered_widget(wid) ) layout.addWidget(widget_btn) else: # No widgets available no_widgets = QLabel("No widgets available") no_widgets.setStyleSheet(f""" color: {c['text_muted']}; font-size: 14px; font-style: italic; margin-top: 24px; """) no_widgets.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.addWidget(no_widgets) # Install info install_info = QLabel("Install plugins from the Plugin Store to add widgets here.") install_info.setStyleSheet(f"color: {c['text_muted']}; font-size: 12px;") install_info.setWordWrap(True) install_info.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.addWidget(install_info) layout.addStretch() return tab def _refresh_widgets_tab(self): """Refresh the widgets tab content dynamically.""" # Find the widgets tab and clear/rebuild it if hasattr(self, 'widgets_tab') and self.widgets_tab: # Clear existing layout layout = self.widgets_tab.layout() if layout: # Remove all widgets while layout.count(): item = layout.takeAt(0) if item.widget(): item.widget().deleteLater() else: # Create new layout from PyQt6.QtWidgets import QVBoxLayout layout = QVBoxLayout(self.widgets_tab) layout.setContentsMargins(24, 24, 24, 24) layout.setSpacing(16) c = get_all_colors() # Rebuild header header = QLabel("🎨 Widgets") header.setStyleSheet(f""" font-size: 24px; font-weight: {EU_TYPOGRAPHY['weight_bold']}; color: {c['text_primary']}; """) layout.addWidget(header) # Description desc = QLabel("Add overlay widgets to your game. Install plugins to get more widgets.") desc.setStyleSheet(f"color: {c['text_secondary']}; font-size: 12px;") desc.setWordWrap(True) layout.addWidget(desc) # Get registered widgets from core.widget_registry import get_widget_registry registry = get_widget_registry() widgets = registry.get_all_widgets() print(f"[Overlay] Refreshing widgets tab - found {len(widgets)} widgets") for w in widgets: print(f" - {w.name} (from {w.plugin_id})") if widgets: # Available widgets section available_header = QLabel("Available Widgets") available_header.setStyleSheet(f""" color: {c['text_primary']}; font-weight: {EU_TYPOGRAPHY['weight_bold']}; font-size: 14px; margin-top: 16px; padding-bottom: 8px; border-bottom: 1px solid {c['border_default']}; """) layout.addWidget(available_header) # Create buttons for each registered widget for widget_info in widgets: widget_btn = self._create_widget_button( f"{widget_info.icon} {widget_info.name}", widget_info.description, lambda wid=widget_info.id: self._add_registered_widget(wid) ) layout.addWidget(widget_btn) else: # No widgets available no_widgets = QLabel("No widgets available") no_widgets.setStyleSheet(f""" color: {c['text_muted']}; font-size: 14px; font-style: italic; margin-top: 24px; """) no_widgets.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.addWidget(no_widgets) # Install info install_info = QLabel("Install plugins from the Plugin Store to add widgets here.") install_info.setStyleSheet(f"color: {c['text_muted']}; font-size: 12px;") install_info.setWordWrap(True) install_info.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.addWidget(install_info) layout.addStretch() def _create_widget_button(self, name: str, description: str, callback) -> QFrame: """Create a widget button card.""" c = get_all_colors() frame = QFrame() frame.setStyleSheet(f""" QFrame {{ background-color: {c['bg_secondary']}; border: 1px solid {c['border_default']}; border-radius: {EU_SIZES['radius_md']}; padding: 4px; }} QFrame:hover {{ border-color: {c['accent_orange']}; background-color: {c['bg_hover']}; }} """) frame.setCursor(Qt.CursorShape.PointingHandCursor) layout = QHBoxLayout(frame) layout.setContentsMargins(16, 12, 16, 12) layout.setSpacing(12) # Name and description text_layout = QVBoxLayout() name_label = QLabel(name) name_label.setStyleSheet(f""" color: {c['text_primary']}; font-weight: {EU_TYPOGRAPHY['weight_bold']}; font-size: 14px; """) text_layout.addWidget(name_label) desc_label = QLabel(description) desc_label.setStyleSheet(f"color: {c['text_secondary']}; font-size: 11px;") desc_label.setWordWrap(True) text_layout.addWidget(desc_label) layout.addLayout(text_layout, 1) # Add button from PyQt6.QtWidgets import QPushButton add_btn = QPushButton("➕ Add") add_btn.setStyleSheet(get_button_style('primary', 'sm')) add_btn.clicked.connect(callback) layout.addWidget(add_btn) return frame def _create_settings_tab(self) -> QWidget: """Create the settings tab content.""" from PyQt6.QtWidgets import QScrollArea, QTabWidget c = get_all_colors() tab = QWidget() layout = QVBoxLayout(tab) layout.setContentsMargins(24, 24, 24, 24) layout.setSpacing(16) # Create tabs for different settings categories settings_tabs = QTabWidget() settings_tabs.setStyleSheet(get_table_style()) # Plugins settings plugins_tab = self._create_plugins_settings_tab() settings_tabs.addTab(plugins_tab, "🔌 Plugins") # Plugin Store store_tab = self._create_plugin_store_tab() settings_tabs.addTab(store_tab, "đŸ“Ļ Plugin Store") # Hotkeys hotkeys_tab = self._create_hotkeys_settings_tab() settings_tabs.addTab(hotkeys_tab, "âŒ¨ī¸ Hotkeys") # Appearance appearance_tab = self._create_appearance_settings_tab() settings_tabs.addTab(appearance_tab, "🎨 Appearance") # About about_tab = self._create_about_tab() settings_tabs.addTab(about_tab, "â„šī¸ About") layout.addWidget(settings_tabs, 1) return tab def _add_registered_widget(self, widget_id: str): """Add a registered widget to the overlay.""" try: from core.widget_registry import get_widget_registry registry = get_widget_registry() # Create widget using registry widget = registry.create_widget(widget_id) if not widget: print(f"[Overlay] Failed to create widget: {widget_id}") return # Position near center of screen screen = QApplication.primaryScreen().geometry() x = (screen.width() - 200) // 2 y = (screen.height() - 100) // 2 widget.move(x, y) # Show and raise (don't activate - widgets have WindowDoesNotAcceptFocus) widget.show() widget.raise_() # Store reference to prevent garbage collection if not hasattr(self, '_active_widgets'): self._active_widgets = [] self._active_widgets.append(widget) print(f"[Overlay] Widget {widget_id} added at ({x}, {y})") except Exception as e: print(f"[Overlay] Error adding widget {widget_id}: {e}") import traceback traceback.print_exc()