"""
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])
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. Drag to position, resize, and configure.")
desc.setStyleSheet(f"color: {c['text_secondary']}; font-size: 12px;")
desc.setWordWrap(True)
layout.addWidget(desc)
# Built-in widgets section
builtin_header = QLabel("Built-in Widgets")
builtin_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(builtin_header)
# Clock widget button
clock_btn = self._create_widget_button(
"â° Clock",
"A customizable clock with 12/24h format and date display",
lambda: self._add_clock_widget()
)
layout.addWidget(clock_btn)
# System monitor widget button
monitor_btn = self._create_widget_button(
"đ System Monitor",
"Monitor CPU and RAM usage in real-time",
lambda: self._add_system_monitor_widget()
)
layout.addWidget(monitor_btn)
# Plugin widgets section
plugin_header = QLabel("Plugin Widgets")
plugin_header.setStyleSheet(f"""
color: {c['text_primary']};
font-weight: {EU_TYPOGRAPHY['weight_bold']};
font-size: 14px;
margin-top: 24px;
padding-bottom: 8px;
border-bottom: 1px solid {c['border_default']};
""")
layout.addWidget(plugin_header)
plugin_info = QLabel("Install plugins from the Plugin Store to add more widgets here.")
plugin_info.setStyleSheet(f"color: {c['text_muted']}; font-size: 12px; font-style: italic;")
plugin_info.setWordWrap(True)
layout.addWidget(plugin_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_clock_widget(self):
"""Add a clock widget to the overlay."""
try:
from core.widget_system import ClockWidget
widget = ClockWidget()
widget.show()
print("[Overlay] Clock widget added")
except Exception as e:
print(f"[Overlay] Error adding clock widget: {e}")
def _add_system_monitor_widget(self):
"""Add a system monitor widget to the overlay."""
try:
from core.widget_system import SystemMonitorWidget
widget = SystemMonitorWidget()
widget.show()
print("[Overlay] System monitor widget added")
except Exception as e:
print(f"[Overlay] Error adding system monitor widget: {e}")