feat: Spotlight-style overlay redesign
- Frosted glass effect with transparency - Rounded corners (20px radius) - Header bar with search icon - Circular plugin icon buttons at bottom - Emoji icons instead of text labels - Subtle shadows and highlights - macOS-style aesthetic - Plugins now sit on transparent background
This commit is contained in:
parent
5e08f56fb2
commit
8dbbf4d971
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
EU-Utility - Overlay Window
|
EU-Utility - Overlay Window
|
||||||
|
|
||||||
Transparent, always-on-top overlay for in-game use.
|
Spotlight-style overlay with frosted glass effect.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -12,10 +12,10 @@ try:
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||||
QLabel, QPushButton, QStackedWidget, QSystemTrayIcon,
|
QLabel, QPushButton, QStackedWidget, QSystemTrayIcon,
|
||||||
QMenu, QApplication, QFrame
|
QMenu, QApplication, QFrame, QGraphicsDropShadowEffect
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt, QTimer, pyqtSignal
|
from PyQt6.QtCore import Qt, QTimer, pyqtSignal, QSize
|
||||||
from PyQt6.QtGui import QAction, QIcon, QKeySequence, QShortcut
|
from PyQt6.QtGui import QAction, QIcon, QColor, QFont
|
||||||
PYQT6_AVAILABLE = True
|
PYQT6_AVAILABLE = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
PYQT6_AVAILABLE = False
|
PYQT6_AVAILABLE = False
|
||||||
|
|
@ -23,7 +23,7 @@ except ImportError:
|
||||||
|
|
||||||
|
|
||||||
class OverlayWindow(QMainWindow):
|
class OverlayWindow(QMainWindow):
|
||||||
"""Transparent overlay window for in-game use."""
|
"""Spotlight-style overlay window."""
|
||||||
|
|
||||||
visibility_changed = pyqtSignal(bool)
|
visibility_changed = pyqtSignal(bool)
|
||||||
|
|
||||||
|
|
@ -35,6 +35,7 @@ class OverlayWindow(QMainWindow):
|
||||||
|
|
||||||
self.plugin_manager = plugin_manager
|
self.plugin_manager = plugin_manager
|
||||||
self.is_visible = False
|
self.is_visible = False
|
||||||
|
self.plugin_buttons = []
|
||||||
|
|
||||||
self._setup_window()
|
self._setup_window()
|
||||||
self._setup_ui()
|
self._setup_ui()
|
||||||
|
|
@ -57,22 +58,19 @@ class OverlayWindow(QMainWindow):
|
||||||
# Transparent background
|
# Transparent background
|
||||||
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
||||||
|
|
||||||
# Size and position
|
# Size and position - wider but shorter for spotlight style
|
||||||
self.resize(800, 600)
|
self.resize(700, 500)
|
||||||
self._center_window()
|
self._center_window()
|
||||||
|
|
||||||
# Click-through when inactive (optional)
|
|
||||||
# self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
|
|
||||||
|
|
||||||
def _center_window(self):
|
def _center_window(self):
|
||||||
"""Center window on screen."""
|
"""Center window on screen."""
|
||||||
screen = QApplication.primaryScreen().geometry()
|
screen = QApplication.primaryScreen().geometry()
|
||||||
x = (screen.width() - self.width()) // 2
|
x = (screen.width() - self.width()) // 2
|
||||||
y = (screen.height() - self.height()) // 2
|
y = (screen.height() - self.height()) // 3 # Upper third like Spotlight
|
||||||
self.move(x, y)
|
self.move(x, y)
|
||||||
|
|
||||||
def _setup_ui(self):
|
def _setup_ui(self):
|
||||||
"""Setup the user interface."""
|
"""Setup the Spotlight-style UI."""
|
||||||
# Central widget
|
# Central widget
|
||||||
central = QWidget()
|
central = QWidget()
|
||||||
self.setCentralWidget(central)
|
self.setCentralWidget(central)
|
||||||
|
|
@ -80,101 +78,186 @@ class OverlayWindow(QMainWindow):
|
||||||
# Main layout
|
# Main layout
|
||||||
layout = QVBoxLayout(central)
|
layout = QVBoxLayout(central)
|
||||||
layout.setContentsMargins(20, 20, 20, 20)
|
layout.setContentsMargins(20, 20, 20, 20)
|
||||||
|
layout.setSpacing(0)
|
||||||
|
|
||||||
# Container with background
|
# Frosted glass container
|
||||||
self.container = QFrame()
|
self.container = QFrame()
|
||||||
self.container.setObjectName("overlayContainer")
|
self.container.setObjectName("spotlightContainer")
|
||||||
self.container.setStyleSheet("""
|
self.container.setStyleSheet("""
|
||||||
#overlayContainer {
|
#spotlightContainer {
|
||||||
background-color: rgba(30, 30, 30, 240);
|
background-color: rgba(40, 40, 40, 180);
|
||||||
border-radius: 10px;
|
border-radius: 20px;
|
||||||
border: 2px solid #444;
|
border: 1px solid rgba(255, 255, 255, 30);
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
# Add shadow effect
|
||||||
|
shadow = QGraphicsDropShadowEffect()
|
||||||
|
shadow.setBlurRadius(30)
|
||||||
|
shadow.setColor(QColor(0, 0, 0, 100))
|
||||||
|
shadow.setOffset(0, 10)
|
||||||
|
self.container.setGraphicsEffect(shadow)
|
||||||
|
|
||||||
container_layout = QVBoxLayout(self.container)
|
container_layout = QVBoxLayout(self.container)
|
||||||
container_layout.setContentsMargins(15, 15, 15, 15)
|
container_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
container_layout.setSpacing(0)
|
||||||
|
|
||||||
# Header
|
# Header with search bar style
|
||||||
header = QHBoxLayout()
|
header = QWidget()
|
||||||
|
header.setStyleSheet("""
|
||||||
|
QWidget {
|
||||||
|
background-color: rgba(255, 255, 255, 15);
|
||||||
|
border-top-left-radius: 20px;
|
||||||
|
border-top-right-radius: 20px;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 20);
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
header_layout = QHBoxLayout(header)
|
||||||
|
header_layout.setContentsMargins(20, 15, 20, 15)
|
||||||
|
header_layout.setSpacing(15)
|
||||||
|
|
||||||
|
# Search icon
|
||||||
|
search_icon = QLabel("🔍")
|
||||||
|
search_icon.setStyleSheet("font-size: 18px; background: transparent; border: none;")
|
||||||
|
header_layout.addWidget(search_icon)
|
||||||
|
|
||||||
|
# Title/Search label
|
||||||
title = QLabel("EU-Utility")
|
title = QLabel("EU-Utility")
|
||||||
title.setStyleSheet("""
|
title.setStyleSheet("""
|
||||||
color: #fff;
|
color: rgba(255, 255, 255, 200);
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
font-weight: bold;
|
font-weight: 500;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
""")
|
""")
|
||||||
header.addWidget(title)
|
header_layout.addWidget(title)
|
||||||
|
|
||||||
header.addStretch()
|
header_layout.addStretch()
|
||||||
|
|
||||||
# Close button
|
# Close button (X) - subtle
|
||||||
close_btn = QPushButton("×")
|
close_btn = QPushButton("×")
|
||||||
close_btn.setFixedSize(30, 30)
|
close_btn.setFixedSize(28, 28)
|
||||||
close_btn.setStyleSheet("""
|
close_btn.setStyleSheet("""
|
||||||
QPushButton {
|
QPushButton {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: #999;
|
color: rgba(255, 255, 255, 150);
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
font-weight: 300;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 15px;
|
border-radius: 14px;
|
||||||
}
|
}
|
||||||
QPushButton:hover {
|
QPushButton:hover {
|
||||||
background-color: #c44;
|
background-color: rgba(255, 255, 255, 20);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
close_btn.clicked.connect(self.hide_overlay)
|
close_btn.clicked.connect(self.hide_overlay)
|
||||||
header.addWidget(close_btn)
|
header_layout.addWidget(close_btn)
|
||||||
|
|
||||||
container_layout.addLayout(header)
|
container_layout.addWidget(header)
|
||||||
|
|
||||||
# Plugin tabs / stack
|
# Plugin content area
|
||||||
|
self.content_area = QWidget()
|
||||||
|
self.content_area.setStyleSheet("background: transparent;")
|
||||||
|
content_layout = QVBoxLayout(self.content_area)
|
||||||
|
content_layout.setContentsMargins(20, 20, 20, 20)
|
||||||
|
content_layout.setSpacing(15)
|
||||||
|
|
||||||
|
# Plugin stack
|
||||||
self.plugin_stack = QStackedWidget()
|
self.plugin_stack = QStackedWidget()
|
||||||
container_layout.addWidget(self.plugin_stack)
|
self.plugin_stack.setStyleSheet("background: transparent;")
|
||||||
|
content_layout.addWidget(self.plugin_stack, 1)
|
||||||
|
|
||||||
# Plugin buttons
|
container_layout.addWidget(self.content_area, 1)
|
||||||
|
|
||||||
|
# Plugin selector bar (at bottom)
|
||||||
if self.plugin_manager:
|
if self.plugin_manager:
|
||||||
self._setup_plugin_buttons(container_layout)
|
self._setup_plugin_bar(container_layout)
|
||||||
|
|
||||||
layout.addWidget(self.container)
|
layout.addWidget(self.container)
|
||||||
|
|
||||||
def _setup_plugin_buttons(self, layout):
|
def _setup_plugin_bar(self, layout):
|
||||||
"""Setup buttons to switch between plugins."""
|
"""Setup circular plugin icon bar at bottom."""
|
||||||
btn_layout = QHBoxLayout()
|
bar = QWidget()
|
||||||
|
bar.setStyleSheet("""
|
||||||
for plugin_id, plugin in self.plugin_manager.get_all_plugins().items():
|
QWidget {
|
||||||
btn = QPushButton(plugin.name)
|
background-color: rgba(0, 0, 0, 40);
|
||||||
btn.setStyleSheet("""
|
border-bottom-left-radius: 20px;
|
||||||
QPushButton {
|
border-bottom-right-radius: 20px;
|
||||||
background-color: #333;
|
|
||||||
color: white;
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
QPushButton:hover {
|
|
||||||
background-color: #444;
|
|
||||||
}
|
|
||||||
QPushButton:pressed {
|
|
||||||
background-color: #555;
|
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
|
bar_layout = QHBoxLayout(bar)
|
||||||
|
bar_layout.setContentsMargins(20, 12, 20, 12)
|
||||||
|
bar_layout.setSpacing(15)
|
||||||
|
bar_layout.addStretch()
|
||||||
|
|
||||||
|
# Add plugin icons
|
||||||
|
plugin_icons = {
|
||||||
|
"Universal Search": "🔍",
|
||||||
|
"Calculator": "🧮",
|
||||||
|
"Spotify": "🎵",
|
||||||
|
"Nexus Search": "🌐",
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, (plugin_id, plugin) in enumerate(self.plugin_manager.get_all_plugins().items()):
|
||||||
|
btn = QPushButton()
|
||||||
|
|
||||||
|
# Get icon emoji or default
|
||||||
|
icon = plugin_icons.get(plugin.name, "⚡")
|
||||||
|
btn.setText(icon)
|
||||||
|
|
||||||
|
btn.setFixedSize(44, 44)
|
||||||
|
btn.setStyleSheet("""
|
||||||
|
QPushButton {
|
||||||
|
background-color: rgba(255, 255, 255, 15);
|
||||||
|
color: white;
|
||||||
|
font-size: 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 22px;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 30);
|
||||||
|
}
|
||||||
|
QPushButton:checked, QPushButton:pressed {
|
||||||
|
background-color: #4a9eff;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
btn.setCheckable(True)
|
||||||
|
btn.setToolTip(plugin.name)
|
||||||
|
|
||||||
# Add plugin UI to stack
|
# Add plugin UI to stack
|
||||||
try:
|
try:
|
||||||
plugin_ui = plugin.get_ui()
|
plugin_ui = plugin.get_ui()
|
||||||
if plugin_ui:
|
if plugin_ui:
|
||||||
|
plugin_ui.setStyleSheet("background: transparent;")
|
||||||
self.plugin_stack.addWidget(plugin_ui)
|
self.plugin_stack.addWidget(plugin_ui)
|
||||||
btn.clicked.connect(
|
btn.clicked.connect(
|
||||||
lambda checked, idx=self.plugin_stack.count()-1:
|
lambda checked, i=idx, b=btn: self._switch_plugin(i, b)
|
||||||
self.plugin_stack.setCurrentIndex(idx)
|
|
||||||
)
|
)
|
||||||
btn_layout.addWidget(btn)
|
self.plugin_buttons.append(btn)
|
||||||
|
bar_layout.addWidget(btn)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error loading UI for {plugin.name}: {e}")
|
print(f"Error loading UI for {plugin.name}: {e}")
|
||||||
|
|
||||||
btn_layout.addStretch()
|
bar_layout.addStretch()
|
||||||
layout.addLayout(btn_layout)
|
layout.addWidget(bar)
|
||||||
|
|
||||||
|
# Select first plugin by default
|
||||||
|
if self.plugin_buttons:
|
||||||
|
self.plugin_buttons[0].setChecked(True)
|
||||||
|
|
||||||
|
def _switch_plugin(self, index, button):
|
||||||
|
"""Switch to selected plugin."""
|
||||||
|
# Uncheck all other buttons
|
||||||
|
for btn in self.plugin_buttons:
|
||||||
|
if btn != button:
|
||||||
|
btn.setChecked(False)
|
||||||
|
|
||||||
|
# Ensure clicked button stays checked
|
||||||
|
button.setChecked(True)
|
||||||
|
|
||||||
|
# Switch stack
|
||||||
|
self.plugin_stack.setCurrentIndex(index)
|
||||||
|
|
||||||
def _setup_tray(self):
|
def _setup_tray(self):
|
||||||
"""Setup system tray icon."""
|
"""Setup system tray icon."""
|
||||||
|
|
@ -187,18 +270,30 @@ class OverlayWindow(QMainWindow):
|
||||||
|
|
||||||
# Tray menu
|
# Tray menu
|
||||||
tray_menu = QMenu()
|
tray_menu = QMenu()
|
||||||
|
tray_menu.setStyleSheet("""
|
||||||
|
QMenu {
|
||||||
|
background-color: rgba(40, 40, 40, 240);
|
||||||
|
color: white;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 30);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
QMenu::item {
|
||||||
|
padding: 8px 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
QMenu::item:selected {
|
||||||
|
background-color: #4a9eff;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
|
||||||
show_action = QAction("Show", self)
|
show_action = QAction("🔍 Show EU-Utility", self)
|
||||||
show_action.triggered.connect(self.show_overlay)
|
show_action.triggered.connect(self.show_overlay)
|
||||||
tray_menu.addAction(show_action)
|
tray_menu.addAction(show_action)
|
||||||
|
|
||||||
hide_action = QAction("Hide", self)
|
|
||||||
hide_action.triggered.connect(self.hide_overlay)
|
|
||||||
tray_menu.addAction(hide_action)
|
|
||||||
|
|
||||||
tray_menu.addSeparator()
|
tray_menu.addSeparator()
|
||||||
|
|
||||||
quit_action = QAction("Quit", self)
|
quit_action = QAction("❌ Quit", self)
|
||||||
quit_action.triggered.connect(self.quit_app)
|
quit_action.triggered.connect(self.quit_app)
|
||||||
tray_menu.addAction(quit_action)
|
tray_menu.addAction(quit_action)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue