EU-Utility/core/overlay_window.py

1768 lines
64 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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("""
<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 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])
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 _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
widget.show()
widget.raise_()
widget.activateWindow()
# 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()